10 その他の考慮事項
この項では、ガベージ・コレクションに影響するその他の状況について説明します。
ファイナライズと弱参照、ソフト参照およびファントム参照
一部のアプリケーションは、ファイナライズと弱参照、ソフト参照またはファントム参照を使用してガベージ・コレクションと相互作用します。
ただし、ファイナライズの使用は推奨されません。セキュリティ、パフォーマンスおよび信頼性の問題が発生する可能性があります。たとえば、ファイナライズによってファイル記述子を閉じると、外部リソース(記述子)がガベージ・コレクションの機敏性に依存するようになります。
ノート:
ファイナライズはJDK 9で非推奨になりました。また、JDK 18では削除のために非推奨になりました。JEP 421: 削除のためのファイナライズの非推奨化に関する項を参照してください。
ファイナライズ
クラスはファイナライザ(protected void finalize()メソッド)を宣言できます。このメソッドの本体では基礎となるリソースを解放します。GCは、GCがオブジェクトのメモリーを回収する前に呼び出される、到達不能オブジェクトのファイナライザをスケジュールします。
オブジェクトは到達不能になるため、GCルートからオブジェクトへのパスがない場合、ガベージ・コレクションの対象になります。GCルートには、アクティブなスレッドからの参照および内部JVM参照が含まれます。これらは、オブジェクトをメモリーに保持する参照です。
ファイナライズ可能なオブジェクトがシステムで構築されているかどうかを確認するには、『Java Platform, Standard Editionトラブルシューティング・ガイド』のファイナライズを保留中のオブジェクトのモニターに関する項を参照してください。また、次のいずれかのツールを使用することもできます:
- JDK Mission Control:
- JVMブラウザで、JVMを右クリックして「JMXコンソールを開始します」を選択します。
- MBeanブラウザのMBeanツリーで、java.langを展開し、「メモリー」を選択します。
- 「MBeanの機能」の属性ObjectPendingFinalizationCountは、ファイナライズが保留中のオブジェクトの概数です。
jcmd
ツール:-
次のコマンドを実行して、Javaファイナライズ・キューに関する情報を出力します。値
<pid>
はJVMのPIDです:jcmd <pid> GC.finalizer_info
-
- JDK Flight Recorder:
- JDK Flight Recorder (JFR)イベント
jdk.FinalizerStatistics
は、実行時にファイナライザを使用するクラスを識別します。イベントは、default.jfc
およびprofile.jfc
JFR構成ファイルでデフォルトで有効化されます。有効にすると、JFRは、空でないfinalize()メソッドを使用して、インスタンス化された各クラスに対してjdk.FinalizerStatistics
イベントを発行します。イベントには、finalize()をオーバーライドするクラス、そのクラスのCodeSource
、クラスのファイナライザが実行された回数、およびヒープに残っている(そしてまだファイナライズされていない)オブジェクト数が含まれます。詳細は、『Java Platform, Standard Edition JDK Mission Controlユーザーズ・ガイド』のフライト・レコーダに関する項を参照してください。
- JDK Flight Recorder (JFR)イベント
ファイナライズからの移行
ファイナライズを回避するには、次のいずれかの方法を使用します:
try-with-Resources文
try
-with-resources文は、1つ以上のリソースを宣言するtry
文です。リソースは、プログラムでの使用が終わったら閉じる必要があるオブジェクトです。try
-with-resources文で、1つ以上の例外が発生した場合でも、各リソースがコード・ブロックの最後に確実にクローズされるようにします。詳細は、Try-with-resources文に関する項を参照してください。
クリーナAPI
アプリケーション内のリソースのライフサイクルが、try
-with-resources文のスコープを超えると予想される場合は、かわりにクリーナAPIを使用できます。クリーナAPIを使用すると、オブジェクトが到達不能になった後に実行されるオブジェクトのクリーニング・アクションをプログラムで登録できます。
クリーナを使用すると、ファイナライザのデメリットの多くを回避できます:
- よりセキュア: クリーナはオブジェクトを明示的に登録する必要があります。また、クリーニング・アクションではアクセスできないため、オブジェクトの再復活は不可能です。
- パフォーマンスの向上: クリーニング・アクションを登録すると、より細かく制御できます。これは、初期化されていないオブジェクトまたは部分的に初期化されたオブジェクトがクリーニング・アクションによって処理されないことを意味します。オブジェクトのクリーニング・アクションを取り消すこともできます。
- より高い信頼性: クリーニング・アクションを実行するスレッドを制御できます。
ただし、ファイナライザと同様に、ガベージ・コレクタはクリーニング・アクションをスケジュールするため、バインドされていない遅延が発生する可能性があります。したがって、リソースのタイムリなリリースが必要な状況では、クリーナAPIを使用しないでください。
クリーナの単純な例を次に示します。次を実行します。
- クリーニング・アクション・クラス
State
を定義します。このクラスでは、クリーニング・アクションを初期化し、(State::run()
メソッドをオーバーライドすることで)クリーニング・アクション自体を定義します。 Cleaner
のインスタンスを作成します。Cleaner
のこのインスタンスで、オブジェクトmyObject1
およびクリーニング・アクション(State
のインスタンス)を登録します。- ガベージ・コレクタがクリーナをスケジュールし、例が終了する前にクリーニング・アクション
State::run()
が実行されるようにするには、例で次のようにします:
図10-1 CleanerExample
import java.lang.ref.Cleaner;
public class CleanerExample {
// This Cleaner is shared by all CleanerExample instances
private static final Cleaner CLEANER = Cleaner.create();
private final State state;
public CleanerExample(String id) {
state = new State(id);
CLEANER.register(this, state);
}
// Cleaning action class for CleanerExample
private static class State implements Runnable {
final private String id;
private State(String id) {
this.id = id;
System.out.println("Created cleaning action for " + this.id);
}
@Override
public void run() {
System.out.println("Cleaner garbage collected " + this.id);
}
}
public static void main(String[] args) {
CleanerExample myObject1 = new CleanerExample("myObject1");
// Make myObject1 unreachable
myObject1 = null;
System.out.println("-- Give the GC a chance to schedule the Cleaner --");
for (int i = 0; i < 100; i++) {
// Calling System.gc() in a loop is usually sufficient to trigger
// cleanup in a small program like this.
System.gc();
try {
Thread.sleep(1);
} catch (InterruptedException e) {}
}
System.out.println("-- Finished --");
}
}
この例の出力は次のとおりです。
Created cleaning action for myObject1
-- Give the GC a chance to schedule the Cleaner --
Cleaner garbage collected myObject1
-- Finished --
本番環境用にクリーナを実装する場合は、次のことを考慮してください:
- クリーニング・アクション・クラス(この例では
State
)は、プライベート実装の詳細である必要があります。特に、main(String[])
メソッドからは使用しないでください。したがって、クリーニング・アクション・クラスは、できるだけ不変である必要があります。新しいオブジェクトは、独自のクリーニング・アクション・クラスの作成と、そのコンストラクタ内でのクリーナの登録を処理する必要があります。 - 通常、クラスはクリーナ・アクション・クラス内のオブジェクトにアクセスできる必要があります。これを行う最も簡単な方法は、オブジェクトがクリーナ・アクション・クラスへの参照を保存することです。
- Cleanerインスタンスを共有する必要があります。この例では、
CleanerExample
のすべてのインスタンスが単一の静的Cleanerインスタンスを共有する必要があります。
クリーナの実装の詳細は、Cleanerクラスに関するJavaDoc APIドキュメントを参照してください。
参照オブジェクト・タイプ
SoftReference、WeakReferenceおよびPhantomReferenceの3つの参照オブジェクト・タイプがあります。参照オブジェクト・タイプはそれぞれ異なるレベルの到達可能性に対応します。次に、オブジェクトのライフサイクルを反映する、最強から最弱までの様々な到達可能性レベルを示します:
- 強到達可能なオブジェクトとは、参照オブジェクトをトラバースすることなく、あるスレッドによって到達できるオブジェクト。新しく作成されたオブジェクトは、それを作成したスレッドにより強到達可能。
- ソフト到達可能なオブジェクトとは、強到達可能ではないが、ソフト参照をトラバースすることで到達できるオブジェクト。
- 弱到達可能なオブジェクトとは、強到達可能でもソフト到達可能でもないが、弱参照をトラバースすることで到達できるオブジェクト。弱到達可能なオブジェクトへの弱参照がクリアされると、そのオブジェクトはファイナライズの対象となる。
- ファントム到達可能オブジェクトとは、強到達可能でも、ソフト到達可能でも、弱到達可能でもなく、ファイナライズされており、ファントム参照がそれを指しているオブジェクト。
- オブジェクトがどのようにも到達できない場合、そのオブジェクトは到達不可能であり、そのため再生の対象となる。
各参照オブジェクト・タイプは、リファレントと呼ばれる特定のオブジェクトへの単一の参照をカプセル化します。参照オブジェクトは、リファレントをクリアするためのメソッドを提供します。
参照オブジェクト・インスタンスの最も一般的な使用方法は次のとおりです:
- システムがメモリーを解放する必要がある場合(必要に応じて再生成できるキャッシュされた値など)にガベージ・コレクションを許可しながらオブジェクトへのアクセスを維持する
- オブジェクトが特定の到達可能性レベルに達したときに、それを判別してなんらかのアクションを実行する(ReferenceQueueクラスと組み合せる)
明示的なガベージ・コレクション
アプリケーションがガベージ・コレクションと相互作用できるもう1つの方法は、System.gc()
を使用して、すべてのガベージ・コレクションを明示的に呼び出す方法です。
これを行うと、必要ない場合(たとえば、マイナー・コレクションで十分な場合)でもメジャー・コレクションの実行が強制されるので、通常は使用しないことをお薦めします。明示的なガベージ・コレクションによるパフォーマンスへの影響を測定するには、フラグ-XX:+DisableExplicitGC
を使用してVMにSystem.gc()
のコールを無視させ、明示的なガベージ・コレクションを無効にします。
明示的なガベージ・コレクションが最もよく使用される例の1つが、リモート・メソッド呼出し(RMI)の分散ガベージ・コレクション(DGC)です。RMIを使用するアプリケーションは、他の仮想マシンのオブジェクトを参照します。これらの分散アプリケーションでは、ローカル・ヒープのガベージ・コレクションを時々呼び出さないとガベージを収集できないので、RMIがフル・コレクションを定期的に実行します。これらのコレクションの実行間隔はプロパティで制御できます(次の例を参照)。
java -Dsun.rmi.dgc.client.gcInterval=3600000
-Dsun.rmi.dgc.server.gcInterval=3600000 ...
この例では、明示的なガベージ・コレクションの実行間隔をデフォルトの1回/分ではなく、1回/時間に指定します。ただし、これでも一部のオブジェクトが回収されるまでに長時間かかる場合があります。DGCのタイムリな処理に上限を設定する必要なければ、これらのプロパティの値をLong.MAX_VALUE
まで引き上げ、明示的なコレクションの時間間隔を事実上無限大にすることができます。
ソフト参照
サーバー仮想マシンでは、ソフト参照がクライアント仮想マシンよりも長くライブに保たれます。
クリア間隔を制御するには、コマンド行オプション-XX:SoftRefLRUPolicyMSPerMB=
<N>
を使用して、ソフト参照を(強到達可能ではなくなってから)ライブに保つ期間を、ヒープ内の空き領域1MB当たりのミリ秒(ms)で指定します。デフォルト値は1000ms/MBで、これは、(オブジェクトへの最後の強参照が収集された後の)ソフト参照がヒープ内の空き領域1MB当たり1秒の寿命であることを意味します。ソフト参照は、散発的に発生するガベージ・コレクション時にのみクリアされるので、これは概算値です。
クラス・メタデータ
JavaクラスにはJava Hotspot VM内の内部表現があり、それはクラス・メタデータと呼ばれます。
以前のリリースのJava Hotspot VMで、クラス・メタデータはPermanentと呼ばれる永続的世代に割り当てられていました。JDK 8以降、永続的(Permanent)世代が削除され、クラス・メタデータがネイティブ・メモリーに割り当てられます。クラス・メタデータに使用できるネイティブ・メモリーの容量はデフォルトで無制限です。クラス・メタデータに使用するネイティブ・メモリーの容量に上限を設定するには、オプション-XX:MaxMetaspaceSize
を使用します。
Java Hotspot VMではメタデータ用の領域を明示的に管理します。OSから領域が要求され、その領域がチャンクに分割されます。そのチャンクからクラス・ローダーがメタデータに領域を割り当てます(チャンクは特定のクラス・ローダーにバインドされます)。クラス・ローダーのクラスがアンロードされると、そのチャンクは再利用に回されるか、OSに返されます。メタデータは、malloc
ではなくmmap
によって割り当てられた領域を使用します。
-XX:UseCompressedOops
をオンにして-XX:UseCompressedClassesPointers
を使用する場合、ネイティブ・メモリーの論理的に異なる2つの領域がクラス・メタデータに使用されます。-XX:UseCompressedClassPointers
は、Javaオブジェクト参照の場合の-XX:UseCompressedOops
と同様に、32ビットのオフセットを使用してクラス・ポインタを64ビットプロセスで表現します。このような圧縮クラス・ポインタにリージョンが割り当てられます(32ビット・オフセット)。リージョンのサイズは-XX:CompressedClassSpaceSize
で設定でき、デフォルトは1ギガバイト(GB)です。圧縮クラス・ポインタの領域は、初期化時に-XX:mmap
によって割り当てられた領域として確保され、必要に応じてコミットされます。-XX:MaxMetaspaceSize
は、コミットされた圧縮クラスの領域と他のクラス・メタデータの領域の合計に適用されます。
クラス・メタデータは対応するJavaクラスのアンロード時に割当て解除されます。Javaクラスはガベージ・コレクションに伴ってアンロードされるので、クラスのアンロードとクラス・メタデータの割当て解除を行うためにガベージ・コレクションが誘発される場合があります。クラス・メタデータ用にコミットされた領域が一定のレベル(最高水位標)に到達すると、ガベージ・コレクションが発生します。ガベージ・コレクション後、クラス・メタデータから解放された領域量に応じて、最高水位標が上げられたり、下げられたりすることがあります。別のガベージ・コレクションがすぐに発生しないように、最高水位標は上げられます。最高水位標の初期値は、コマンド行オプション-XX:MetaspaceSize
の値に設定されます。-XX:MaxMetaspaceFreeRatio
オプションと-XX:MinMetaspaceFreeRatio
オプションを基に、これを上げ/下げします。クラス・メタデータで使用可能なコミットされた領域(クラス・メタデータ用にコミットされた総領域の割合)が-XX:MaxMetaspaceFreeRatio
より大きいと、最高水位標は下げられます。これが-XX:MinMetaspaceFreeRatio
より低いと、最高水位標は上げられます。
クラス・メタデータによるガベージ・コレクションの早期誘発を避けるには、オプション-XX:MetaspaceSize
に高めの値を指定します。アプリケーションに割り当てられるクラス・メタデータの容量はアプリケーションに依存するので、 -XX:MetaspaceSize
の選択に一般的なガイドラインはありません。-XX:MetaspaceSize
のデフォルト・サイズは、12MBから約20MBの範囲でプラットフォームに依存します。
メタデータに使用される領域に関する情報はヒープの出力に含まれます。出力は、通常次のようになります。
[0,296s][info][gc,heap,exit] Heap
[0,296s][info][gc,heap,exit] garbage-first heap total 514048K, used 0K [0x00000005ca600000, 0x00000005ca8007d8, 0x00000007c0000000)
[0,296s][info][gc,heap,exit] region size 2048K, 1 young (2048K), 0 survivors (0K)
[0,296s][info][gc,heap,exit] Metaspace used 2575K, capacity 4480K, committed 4480K, reserved 1056768K
[0,296s][info][gc,heap,exit] class space used 238K, capacity 384K, committed 384K, reserved 1048576K
「Metaspace
」で始まる行のused
値は、ロード済クラスが使用している領域量です。capacity
値は、現在割り当てられているチャンク内のメタデータで使用可能な領域です。committed
値は、チャンクの使用可能な領域量です。reserved
値は、メタデータ用に確保された(必ずしもコミットされているとはかぎらない)領域量です。class space
で始まる行には、圧縮クラス・ポインタのメタデータの対応する値が含まれています。