1-1-5-2: オブジェクトの管理


・ Javaのシステムでは、メモリの管理は原則としてシステムの側が行います。 これによってプログラミングの労力はかなり軽減されます。 Javaではメモリの確保や解放を直接記述することはありません。 Cのように「ポインタ変数の操作」も行いません。 アプリケーションは単に JVM(Java仮想マシン)に対して オブジェクトの生成が必要なことを知らせるだけでいいのです。 ただし当然のことながら効率の良い動作を保証するためには、 システムの側に工夫が必要となるでしょう。
特に問題となるのはメモリーの解放の仕組みです。 アプリケーションが使用を終えたメモリー領域を捜し出し、 それを解放するのは JVMの仕事になります。 これがいわゆる「ガーベジ・コレクション(Garbage Collection)」の考え方です。 ただし、この作業のタイミングを決めるのは難しい問題です。 ガーベジ・コレクションを行っている間、 アプリケーションの応答が一時的に損なわれる恐れがあるからです。 JVMはこの作業を独立したスレッドによって実行することで問題を解決しています。 アプリケーションの実行と並列して 常に陰でガーベジ・コレクションの機構が働いているわけです。

・ さて以上のことを頭に入れた上で、 次にオブジェクトのデータがどのように取り扱われているのかを、 Javaのプログラムの記述も含めて見てみましょう。 ポイントは次の2つの存在の違いと両者の関係です。

Javaのプログラムに現れる変数は、 オブジェクトの実体そのものを記憶しているわけではありません。 より正確に言えばオブジェクトへの参照を記憶します。 この違いは非常に重要なことで、Javaのプログラムの記述に大きく影響してきます。 まず、オブジェクトの実体と変数は1対1に対応するわけではありません。 多くの変数が同一のオブジェクトの実体を参照することができるからです。 たとえば次のような例を考えてみましょう。


Color c1 = new Color( 255, 0, 0 );
Color c2 = c1;
Color c3 = c2;

3個の Colorのクラスの変数が宣言され、値が代入されています。 しかし、実際に生成されるオブジェクトは最初の1個だけです。 c2もc3も単に c1が記憶している参照の値を代入されることで 同一のオブジェクトの実体を参照することになります。 それぞれ独立したオブジェクトの実体を持つわけではありませんし、 そのためにオブジェクトの内容をコピーするというような処理は行われません。
1つの変数が同時に複数のオブジェクトの実体を参照することはあり得ませんが、 処理の途中で参照先のオブジェクトが変更されることは珍しくありません。


Color c1 = new Color( 255, 0, 0 );
Color c2 = new Color( 0, 255, 0 );
Color c3 = c2;
c2 = c1;
c1 = c3;

上の処理では、最初に c1 と c2 が参照していたオブジェクトの実体が交換されます。 ただし、 この例でもオブジェクトの内容のコピーや新しいオブジェクトの生成が行われている わけではない点に注意してください。 参照の値が交換されただけで、オブジェクトの実体の側には何の変化もありません。

・ 参照であるかオブジェクトの実体であるかの違いは、 メソッドの引数や返値としてやり取りされる場合に特に重要です。


/** 第1引数の Frame の背景色を第2引数の Color に変更する */

public void changeColor( Frame frame, Color color ) {

     frame.setBackground( color );
}
       

上のメソッドの行った処理は、 呼び出し側で指定した Frameのオブジェクトに対してきちんと反映されます。 引数に渡されたのはオブジェクトの実体のコピーではなく、 参照の値のコピーだからです。そのため操作の対象となる Frameのオブジェクトは 呼び出し側もメソッド側も同じ実体になります。


/** 第1引数の Color を背景色とする Frameのオブジェクトを返す */

public Frame coloredFrame( Color color ) {

     Frame frame = new Frame( "Colored Frame" );
     frame.setBackground( color );
     return frame;
}
       

この例でも、返値として返された Frameのオブジェクトは、 呼び出し側でちゃんと利用できます。 一見すると frame はローカル変数なので、 メソッドの処理を終えた後もオブジェクトが存在し続けるのか心配です。 しかし、メソッドの中で寿命を終えるのはオブジェクトへの参照を記憶した frame という変数にすぎません。オブジェクトの実体は寿命を終えるわけでは ありません。
このように、オブジェクトの実体の寿命は一般にブロック構造とは独立です。 オブジェクトの実体の寿命を決めるのは、 「その実体を参照している変数が存在しているかどうか?」ということです。


Color c1 = new Color( 255, 0, 0 );
Color c2 = c1;
Color c3 = c2;
c1 = null;
c2 = null;
c3 = null;

上の例で登場した "null" というキーワードは、 「変数が参照するオブジェクトが存在しない」ということを表します。 "null"はすべてのクラスのオブジェクトへの参照の変数に代入することができます。 さて、上の例では最初の例と同じくオブジェクトの実体は1つだけ存在しています。 そして3個の参照の変数に "null"を順番に代入していきます。 最初の nullの代入、2番目の nullの代入では何も変化はありません。 しかし、最後の nullの代入でオブジェクトを参照する変数がなくなった瞬間、 このオブジェクトの実体は「不要なオブジェクト」ということになります。 そしてガーベジコレクションの対象となるわけです。


Frame frame = new Frame( "Color Frame" );
Color color = new Color( 255, 0, 0 );
frame.setBackground( color );
color = null;

この例は、一見すると上のケースと同じように見えます。 しかし、color に nullの代入が行われても、オブジェクトの実体は存在を続けます。 frameオブジェクトの内部にあるフィールドの変数が、参照を維持しているからです。


Frame frame = new Frame( "Color Frame" );
frame.setBackgroud( new Color( 255, 0, 0 ) );

このケースも同様です。Colorのコンストラクタによって生成されたオブジェクトは 一見すると参照の変数が存在しないように思えますが、やはり frameのオブジェクトの 内部から参照を受けています。 一般にソースプログラムを見ただけでは、 オブジェクトの寿命がいつまで続くかを簡単に判断することはできません。 他のメソッドやクラスのオブジェクトの内部で参照されている可能性があるからです。 実際、それをプログラマがいちいち気にする必要はありません。 JVMのガーベジコレクションの機構が行うべき仕事なのです。

・ この他 JVMの内部の処理では、 ハッシュコード(Hash Code)という概念が利用されています。 ハッシュコードは JVMが効率良くオブジェクトを管理できるように、 各オブジェクトに割り振った一種の識別番号です。 ハッシュコードを用いることでオブジェクトの同一性の判定が高速化され、 オブジェクトの検索などの処理が効率良く行えます。 Javaのオブジェクトは必ず対応するハッシュコードを持ちます。 またクラスの都合に合わせて、 ハッシュコードを計算する方法を定義し直すことも可能です。 同一の内容を持つオブジェクトは必ず同一のハッシュコードを持つように 定義されますが、その逆は必ずしも成り立ちません。 たとえば文字列を表す Stringクラスの場合、その内容は理論上無限の可能性が あるわけで、有限のサイズのハッシュコードで一意に対応付けることは不可能です。 しかし、同一のハッシュコードを持つ異なるオブジェクトが生じる確率を 小さくすることが可能ならば、ハッシュコードの利用は有効です。


ソース内の変数、JVMの中のオブジェクトの実体、ハッシュコードの関係