/*** Copyright: OGIHARA Takeshi, Sept. 2011 ***/

ガーベジコレクションの動作を調べるプログラム

ガーベジコレクションがどのように機能しているかを調べるテスト用のプログラムを作成してみます。このプログラムは大きなサイズのオブジェクトを生成し、外部変数や自動変数、構造体のメンバなどに代入するという動作を繰り返します。解放される直前に finalize メソッド内でオブジェクトの種類を表示させることによって、どんな変数から参照されていると回収されるのかを調べることができます。


リスト1 ガーベジコレクションのテスト用プログラム (gctest.m)
#import <Foundation/NSObject.h>
#import <Foundation/NSGarbageCollector.h>
#import <objc/objc-auto.h>
#import <stdio.h>
#import <stdlib.h>
#import <libc.h>
#define MASS    100000     /* 調整可能 */
#define LOOP    3          /* 調整可能 */

@interface Chunk : NSObject
{
    int group;
    id  target;
    char mass[MASS];
}
+ (void)setGroup:(int)n;
- (id)initWithLabel:(const char *)lab;
- (void)setTarget:(id)obj;
- (void)finalize;
@end

@implementation Chunk

static int grpnum = 0;

+ (void)setGroup:(int)n { grpnum = n; }   /* グループの番号を設定 */

- (id)initWithLabel:(const char *)lab
{
    self = [super init];
    group = grpnum;           /* グループの番号 */
    target = nil;
    strcpy(mass, lab);        /* 引数の文字列を配列にコピーしておく */
    return self;
}

- (void)setTarget:(id)obj { target = obj; }

- (void)finalize {
    printf("%d %s\n", group, mass);   /* グループ番号と文字列を印字 */
    [super finalize];
}

@end

struct pac { /* 単純な構造体 */
    id obj;
};

Chunk *g = nil;               /* Global: 外部変数 */
__weak Chunk *wa = NULL;      /* Weak Reference: 弱い参照 */
struct pac *cp, *mp;          /* 構造体ポインタ */
__strong struct pac *sp;      /* __strong付きの構造体ポインタ */

static void func(void)
{
    static id s = nil;  /* Static var: 関数内の局所的な静的変数 */
    id f;               /* Stack in func: 関数スタック内の自動変数 */

    g = [[Chunk alloc] initWithLabel:"Global"];
    [g setTarget: [[Chunk alloc] initWithLabel:"Global Ivar"]];
                        /* Instance var: インスタンス変数 */
    s = [[Chunk alloc] initWithLabel:"Static var"];
    f = [[Chunk alloc] initWithLabel:"Auto in func"];
    if (wa == nil)
        wa = [[Chunk alloc] initWithLabel:"Weak Reference"];
    cp = NSAllocateCollectable(sizeof(struct pac), NSScannedOption);
    cp->obj = [[Chunk alloc] initWithLabel:"Struct"];
    mp = (struct pac *)malloc(sizeof(struct pac));
    mp->obj = [[Chunk alloc] initWithLabel:"Malloc Struct"];
    sp = NSAllocateCollectable(sizeof(struct pac), NSScannedOption);
    sp->obj = [[Chunk alloc] initWithLabel:"Strong Struct"];
}

int main(void)
{
    int i;
    id m;

//  objc_startCollectorThread(); /* ガーベジコレクタを別スレッドで */
    for (i = 1; i <= LOOP; i++) {
        [Chunk setGroup: i];     /* インスタンスに同じ番号を持たせる */
        m = [[Chunk alloc] initWithLabel:"Auto in main"];
        func();
        printf("------\n");
        [[NSGarbageCollector defaultCollector] collectIfNeeded];
                      /* ガーベジコレクタの起動を要求 */
        sleep(1);
    }
    printf("======\n");
    [[NSGarbageCollector defaultCollector] collectExhaustively];
                      /* ガーベジコレクタの徹底した起動 */
    return 0;
}


まず、このプログラムでは不必要に大きなサイズのインスタンスを生成するクラス Chunk を定義しています。インスタンスの大きさはマクロ MASS で調整できます。このクラスのインスタンスは、イニシャライザで指定された文字列と、生成時にクラスで指定された番号（ループの繰り返し回数）を持ち、解放される時に finalizeメソッドでこれらを書き出します。

プログラムは、main関数から別の関数 func を繰り返し呼び出します。main関数と func 内でさまざまな種類の変数に Chunk のインスタンスを生成して代入します。基本的に、いったん生成され、変数に代入されたインスタンスは、次の繰り返しで同じ変数に別のインスタンスが代入された時に不要なオブジェクトになります。ただし、弱い参照の変数や、自動変数などは挙動が異なります。

実際にこのプログラムを動作させた結果を下に示します。なお、main関数の最初にある objc_startCollectorThread() の呼び出しを有効にすると、ガーベジコレクタが別のスレッドで動作しますが、以下ではガーベジコレクタの働きを分かりやすく示すためにコメントアウトしたままの結果について説明します。「------」という行がガーベジコレクタに起動を求める時点を表します。行の左の数字は何番目のループで生成されたインスタンスなのかを示します。実行環境によって表示順や結果が異なることがあります。

------
1 Malloc Struct
1 Weak Reference
1 Auto in func
------
2 Malloc Struct
2 Weak Reference
2 Auto in func
1 Strong Struct
1 Static var
1 Global Ivar
1 Global
1 Auto in main
------
3 Malloc Struct
3 Weak Reference
3 Auto in func
2 Strong Struct
2 Static var
2 Global Ivar
2 Global
2 Auto in main
======
3 Struct
2 Struct
1 Struct


ガーベジコレクションの最初の起動で解放されたメモリは、関数内の自動変数、弱い参照のインスタンス、malloc( ) で確保した構造体の中のインスタンスです。自動変数に代入されたインスタンスは、関数の実行が終了すると不要になります。弱い参照のポインタ変数 wa には、内容が nil の時だけ新しいインスタンスが代入されるようになっています。２回目の繰り返し以降でも解放が行われていますので、インスタンスが解放される時、変数のゼロ化が行われていることが確認できます。

malloc( ) で確保したメモリ領域は回収対象ではありません。その構造体のメンバに代入されたインスタンスはルート集合に関係していないため、毎回すぐに回収されてしまいます。しかも、このポインタはぶらさがりポインタになります。

実行結果の左側の数字に注意すると、ガーベジコレクションの２回目、３回目の起動で、それぞれの前の回のループ内で生成されたインスタンスが解放されているのが分かります。それらは、main関数内の自動変数、外部変数とそのオブジェクトのインスタンス変数、関数内の静的変数、および強い参照のポインタで指された構造体です。これらは前の回のループで、変数に別のポインタの値が代入されたことによって回収対象になったものです。

最後の「======」の下は、ガーベジコレクタに対して徹底的な回収を要求した結果、解放されたインスタンスです。関数 NSAllocateCollectable( ) が確保したメモリ領域は回収の対象ですが、プログラムでは強い参照ではない、通常のポインタ変数 cp へ代入されているため、書き込みバリアで管理できていません。オブジェクトを指す以外のポインタ変数はルート集合には含まれませんので、この構造体もメンバのインスタンスも、いつ回収されるか分かりませんし、回収された後はぶらさがりポインタになります。

一方、同じ NSAllocateCollectable( ) で確保したメモリでも、__strong を付けた変数 sp に代入された構造体は動作が異なります。変数に新しい値が代入された後だけ、以前の領域が解放され、同時にメンバであるオブジェクトが解放されます。３回目のループで確保された領域はルート集合に含まれているため、最後まで回収対象にはなりません。これは、実行例の中に「3 Strong Struct」という文字列が表れないことから分かります。

このプログラムを書き換えてみると、いろいろな場合にガーベジコレクションがどう機能するのかを確認することができます。 objc_startCollectorThread() を呼び出した場合の動作についても調べてみて下さい。なお、実行する環境によってはインスタンスのサイズを調整しないと、繰り返しごとにガーベジコレクタを起動できないかもしれません。
