gcj
11. GC †
- あまり高度な処理は目指さないで、とりあえずdeleteしなくて済めば良い。
- アルゴリズムとしては参照カウントがもっとも手っ取り早い。
- 循環参照という致命的な欠点がある。
- gcjはポインタがスコープから外れるときに特別な処理を挟まないため、参照カウントがうまくいかない。
- メモリの確保時に一定のタイミングでマーク&スイープを行うことが最も単純。
マーク&スイープ †
- 古典的なGCのアルゴリズム。
- 循環参照にも対処できる。
- あるメモリ領域が使われているかどうかを、グローバル(static含む)とスタックのポインタから辿って行けるかどうかで判断する。
- 管理している全メモリ領域に対して、参照を発見次第マークしていき、マークされなかったものを掃除(スイープ)する。
アドレス管理 †
- 管理対象とするアドレスのリストを保持して、合致するかどうかでインスタンスが使用されているかどうかを判定する。
- 比較にはスタックを1バイトずつずらして走査する。
- ポインタを発見したら、それが未マークであればマークして、そのインスタンスの中のポインタを再帰的に調べる。
- 偶然ポインタではないのに合致する可能性が排除できないが、確率的には極めて低く、誤マークにより削除されずに残る以外に実害はないため、無視する。
- 当面はグローバルポインタは登録制とする。
- .dataセクションの範囲、またはグローバルポインタのリストを取得する方法が判明するまでのつなぎ。
アドレスのリスト †
- あるアドレスが登録されているかどうかを高速にチェックする必要がある。
- アドレスを8bitごとに区切って配列をツリー構造にする。
- 登録されていないアドレスはNULLにしておくことでメモリ占有量は抑えられる。
- malloc()がalign16であると仮定すると最下位8bitのうち4bitは無意味になる。
- 管理対象が少ないためここだけはリニアサーチで対応する。
以上の仕様を満たす最低限の実装:
sms_gc.zip
組み込み †
- javalang.ccのmalloc()をsms_gc_malloc()に置き換える。
- 一定以上のメモリが確保されるタイミングでマーク&スイープが実行されるようになる。
main.cc †
#include <java/lang/System.h>
#include <java/io/PrintStream.h>
#include "A.h"
#include <stddef.h>
#include <sms_gc.h>
int main() {
SMS_GC_INIT();
sms_gc_register(&::java::lang::System::out);
::java::lang::System::out = new ::java::io::PrintStream(NULL);
::A::main();
return 0;
}
A.java †
public class A {
public static void main() {
for (int i = 0; i < 100; i++) {
Object obj = new Object();
}
}
}
実行結果 †
$ ./test.exe
sms_gc_register: 0x4084a0
sms_gc_malloc: 0x4c0008
sms_gc_malloc: 0x4c0fc8
(中略)
sms_gc_malloc: 0x4c2120
sms_gc_collect
sms_gc_free: 0x4c0fc8
(中略)
sms_gc_free: 0x4c1ed0
(中略)
sms_gc_malloc: 0x4c18f8
(中略)
sms_gc_malloc: 0x4c1e48
sms_gc_collect
sms_gc_free: 0x4c16b8
(中略)
sms_gc_free: 0x4c1e28
ここまでの作業結果:
11.zip
ついでにDLL化のテスト:
11-dll.zip