放課後プログラミング

調べたことや考えたことなどを忘れないために書きます。

ガベージコレクションの仕組み

Oracleが行っている「JavaSE7 パフォーマンス・チューニング」という研修に行かせてもらえました。
コースの概要はリンク先を参照。

Java SE 7 パフォーマンス・チューニング

知らないことが多くていろいろ学ぶことができたので、このブログに書き留めておきたいと思います。

ガベージコレクションの基本

C++等と違い、JavaではJVMガベージコレクション(以降GC)機能によって参照されなくなった領域をヒープから自動的に開放してくれる。これがどのような仕組みで行われるのかについて記述する。

以下のようなヒープ領域を想定する。(ヒープとはスタックとはみたいな話は省略します)

ヒープ領域
+--------------------------------------------------------------+
|                                                              |
+--------------------------------------------------------------+

オブジェクトを生成するとヒープ上に領域が割り当てられる。
Object obj1 = new Object();

+--------+-----------------------------------------------------+
|  obj1  |                                                     |
+--------+-----------------------------------------------------+

さらに生成するとその分確保される。
Object obj2 = new Object();
Object obj3 = new Object();
Object obj4 = new Object();
Object obj5 = new Object();
Object obj6 = new Object();
Object obj7 = new Object();

+--------+--------+--------+--------+--------+--------+--------+
|  obj1  |  obj2  |  obj3  |  obj4  |  obj5  |  obj6  |  obj7  |
+--------+--------+--------+--------+--------+--------+--------+

ヒープが一杯のになると、新しいオブジェクトのためのヒープ領域を確保するためにGCが発動する。
このときの動作は
1) Mark
参照が残っているかどうかを調べ、残っているものにフラグを立てる。(*がフラグのつもり)(ここでは1,2,5,7の参照だけ残ってると仮定)

+--------+--------+--------+--------+--------+--------+--------+
|* obj1  |* obj2  |  obj3  |  obj4  |* obj5  |  obj6  |* obj7  |
+--------+--------+--------+--------+--------+--------+--------+

2) Sweep
参照の無い領域を開放する。

+--------+--------+--------+--------+--------+--------+--------+
|* obj1  |* obj2  |        |        |* obj5  |        |* obj7  |
+--------+--------+--------+--------+--------+--------+--------+

3) Compaction
ヒープ上でバラバラに残った確保済み領域を寄せる。(大きな領域を確保できるようにするため)

+--------+--------+--------+--------+--------------------------+
|  obj1  |  obj2  |  obj5  |  obj7  |                          |
+--------+--------+--------+--------+--------------------------+

複雑なことを抜きにするとこれがGCの基本的な動作になる。

世代別GC

GCGC用のスレッドで動作し、その間別のスレッドは停止しているため、GCの時間はできる限り短いほうが良い。
上記の簡単なロジックでは無駄が多く時間がかかる。そのため、HotSpot VMでは世代別GCが行われている。世代別GCとは
・多くのオブジェクトは寿命が短い
・一部のオブジェクトは寿命が非常に長い
という2つの経験則を基に効率化されたロジックのGCである。世代別GCでは長く生きたオブジェクトと若いオブジェクトを分けて考える。

+--------Young Generation--------+-----Tenured Generation------+
+----Eden----+Survivor0+Survivor1+
+------------+---------+---------+-----------------------------+
|            |         |         |                             |
+------------+---------+---------+-----------------------------+

ヒープを上記のように分割し、新規にオブジェクトを生成したときはEden領域に領域を確保する
ここで

Young世代

1) Edenが一杯になるとEdenの各オブジェクト参照が残っているかどうかを調べ、残っているものSuvivor0に移動し、Edenを開放する。この時Surivivor0に入りきらなければTenuredに移動する

2) Survivor0とSurvivor1の名前を入れ替える

3) 再びEdenが一杯になると、EdenとSurvivor1の参照が残っているオブジェクトをSurvivor0に移動し、EdenとSurvivor1を開放する。この時Surivivor0に入りきらなければTenuredに移動する

4) Survivor0とSurvivor1の名前を入れ替える

5) 3),4)でJVMに設定した回数(殿堂入り閾値)以上残ったオブジェクトはTenuredに移動する

Tenured世代

1) Tenured領域が一杯になるとヒープ全体に対してMark,Sweep,Compactionを行う

Young世代のGC(マイナーGC)はコピーGCと呼ばれる手法で比較的高速に行われる。Tenured世代のGC(メジャーGC)はFullGCと呼ばれコピーGCの数十倍程度の時間を要する。
このロジックにより、都度FullGCを行うより効率的にGCを行うことができ、プログラムの停止時間を短くすることができる。

各種ヒープの設定

各領域の大きさ等はJVMの起動オプションで指定することにより設定することができる。(指定しない場合はシステム依存)

オプション 説明
-Xms? ヒープの初期サイズ、続けて512kのように書いて指定する。 -Xms128m
-Xmx? ヒープの最大サイズ、続けて128mのように書いて指定する。 -Xms512m
-XX:NewRatio=? young世代とtenured世代のサイズ比率、自然数で指定する。NewRatio=Tenured/Young。 -XX:NewRatio=4
-XX:SurvivorRatio=? Eden領域とSurvivor領域のサイズ比率、自然数で指定する。SurvivorRatio=Eden/2Survivor。 -XX:SurvivorRatio=6
-XX:MaxTenuringThreshold=? 殿堂入り閾値自然数で指定する。 -XX:MaxTenuringThreshold=32

チューニングを行う際はこれらを適切な値を設定してなるべく低コスト(狭いヒープ)で高パフォーマンス(GCによる停止時間が短い)を目指す必要がある。


次の投稿で適切な領域サイズや閾値の設定について書きます。