参照オブジェクト(java.lang.ref.Reference) の処理は 2段階からなる.
差し先のオブジェクトが死んでいた場合, その死んでいるオブジェクトは回収される.
さらに, それを指していた参照オブジェクト自体も (ReferenceQueue に登録されていた場合には) pending list につながれる.
これにより, pending list 中のオブジェクトが対応する java.lang.ref.ReferenceQueue オブジェクトにプッシュされる.
GC 時には, まず普通の参照を辿る処理が行われ, その後に参照オブジェクトだけを改めて辿る処理が行われる (See: here for details).
これは, 普通の参照を辿り終わってからでないと, 参照オブジェクトの指し先が live かを判定できないため (例えば, soft reference の指し先は, 強参照から指されていなければ dead になる).
また, 同様の理由から, 参照オブジェクトの処理も強い順に処理が行われる.
これらの処理は, 参照を辿る処理の途中で参照オブジェクトを見つけると, 見つけた参照オブジェクトをリスト上につないでいくことで実現されている. 参照を辿り終わった後でリストを辿っていけば, 見つけた参照オブジェクトを全て処理できる.
なお, このリストは参照オブジェクトの discovered フィールドを使って構築される. また, 参照オブジェクトの種類毎に異なるリストが使われる (これにより参照オブジェクトの処理時に参照の強い順に処理するのが簡単になっている). さらに, マルチスレッドで処理できるようスレッド毎にリストが用意されている. このため「参照オブジェクトの種別×スレッド数」分だけのリストがある.
これらのリストの先頭は ReferenceProcessor オブジェクト内の以下のフィールドになっている (これらのフィールド自体は DiscoveredList クラスの配列となっている. 配列となっているのは, スレッドの数分だけ存在するため. 下の表記では "[id]" というのがスレッド数だけあることを示す. DiscoveredList という名前だが, 実際にはこれはリストの先頭になるだけで, そこから先はつながっている参照オブジェクト自身の discovered フィールドを使って中身をつないでいる)
soft referece の場合 ReferenceProcessor::_discoveredSoftRefs[id]
weak reference ReferenceProcessor::_discoveredWeakRefs[id]
final reference ReferenceProcessor::_discoveredFinalRefs[id]
phantom reference ReferenceProcessor::_discoveredPhantomRefs[id]
これらの処理が終わった後, ...
Reference Handler は Java のクラスとして実装されており, 具体的なクラス名としては java.lang.ref.Reference$ReferenceHandler になる.
Reference Handler は起動時に生成される. HotSpot の実行中は常時無限ループして pending list を監視しており, 要素が追加されたら対応する ReferneceQueue に登録している.
なお, pending list は java.lang.ref パッケージ内の pending という static 変数に格納されている.
((cite: jdk/src/share/classes/java/lang/ref/Reference.java))
/* List of References waiting to be enqueued. The collector adds
* References to this list, while the Reference-handler thread removes
* them. This list is protected by the above lock object.
*/
private static Reference pending = null;
java.lang.ref.Reference オブジェクトは, 以下のように状態遷移する.
Active ----> Pending ----> Enqueued ----.
| |
| V
-----------------------------------> Inactive
Active
どの参照オブジェクトも, 生成された直後の状態は Active 状態. 参照オブジェクトの差し先が死亡すると Pending か Inactive に変わる.
(その参照オブジェクトが ReferenceQueue に登録されていれば Pending, 登録されていなければ Inactive. なお Pending に変わる場合は, GC によってその参照オブジェクトは pending list に追加される)
Pending
pending list に追加された後, Reference Handler スレッドによって ReferenceQueue にプッシュされるまでの状態.
Enqueued
Reference Handler スレッドによって ReferenceQueue にプッシュされた後, ReferenceQueue から取り出されるまでの状態.
Inactive
もう実行する処理がない状態. 一度この状態になると状態が変わることはない.
現在どの状態にあるかは queue フィールドと next フィールドを見れば分かるようになっている.
Active
next フィールドが null なら Active 状態. queue フィールドには, 登録されている ReferenceQueue オブジェクトのポインタが入っている. (ReferenceQueue に登録されていない参照オブジェクトの場合は, queue には ReferenceQueue.NULL が入っている)
Pending
queue フィールドには, 登録されている ReferenceQueue オブジェクトのポインタが入っている. next フィールドは, pending list に登録されている次の参照オブジェクトを指す. (自分が最後の場合は, 自分自身を指す)
Enqueued
queue フィールドが ReferenceQueue.ENQUEUED なら Enqueued 状態. next フィールドは, その ReferenceQueue オブジェクトにプッシュされている次の参照オブジェクトを指す. (自分が最後の場合は, 自分自身を指す)
Inactive
queue フィールドが ReferenceQueue.NULL で next フィールドが自分自身を指していたら Inactive 状態.
なお Concurrent な GC の場合, GC に並行して Mutator が java.lang.ref.Reference.enqueue() を呼び出しているかもしれない. こんな状態でもちゃんと Active 状態の参照オブジェクトを見つけられるように, GC 中に見つけた参照オブジェクトについては discovered フィールドでリンク状につないで管理している.
((cite: jdk/src/share/classes/java/lang/ref/Reference.java))
/* A Reference instance is in one of four possible internal states:
*
* Active: Subject to special treatment by the garbage collector. Some
* time after the collector detects that the reachability of the
* referent has changed to the appropriate state, it changes the
* instance's state to either Pending or Inactive, depending upon
* whether or not the instance was registered with a queue when it was
* created. In the former case it also adds the instance to the
* pending-Reference list. Newly-created instances are Active.
*
* Pending: An element of the pending-Reference list, waiting to be
* enqueued by the Reference-handler thread. Unregistered instances
* are never in this state.
*
* Enqueued: An element of the queue with which the instance was
* registered when it was created. When an instance is removed from
* its ReferenceQueue, it is made Inactive. Unregistered instances are
* never in this state.
*
* Inactive: Nothing more to do. Once an instance becomes Inactive its
* state will never change again.
*
* The state is encoded in the queue and next fields as follows:
*
* Active: queue = ReferenceQueue with which instance is registered, or
* ReferenceQueue.NULL if it was not registered with a queue; next =
* null.
*
* Pending: queue = ReferenceQueue with which instance is registered;
* next = Following instance in queue, or this if at end of list.
*
* Enqueued: queue = ReferenceQueue.ENQUEUED; next = Following instance
* in queue, or this if at end of list.
*
* Inactive: queue = ReferenceQueue.NULL; next = this.
*
* With this scheme the collector need only examine the next field in order
* to determine whether a Reference instance requires special treatment: If
* the next field is null then the instance is active; if it is non-null,
* then the collector should treat the instance normally.
*
* To ensure that concurrent collector can discover active Reference
* objects without interfering with application threads that may apply
* the enqueue() method to those objects, collectors should link
* discovered objects through the discovered field.
*/
ReferenceHandler スレッドの処理が Garbage Collector と競合するとまずいため, lock という static フィールドに Lock 型の値が格納されている.
競合する恐れがある処理はこの lock で排他して行う (といっても現状では java.lang.ref.Reference$ReferenceHandler.run() でしか使われてないが...).
なお GC との排他なので, このロックを握っているときに長時間の処理を行ったりメモリ確保を試みたりしてはいけない, とのこと.
((cite: jdk/src/share/classes/java/lang/ref/Reference.java))
/* Object used to synchronize with the garbage collector. The collector
* must acquire this lock at the beginning of each collection cycle. It is
* therefore critical that any code holding this lock complete as quickly
* as possible, allocate no new objects, and avoid calling user code.
*/
static private class Lock { };
private static Lock lock = new Lock();
(HotSpot の起動時処理) (See: here for details) -> Threads::create_vm() -> init_globals() -> referenceProcessor_init() -> ReferenceProcessor::init_statics()
-> java.lang.ref.Reference クラスの static ブロックでの処理 -> java.lang.ref.Reference$ReferenceHandler.<init>() -> java.lang.Thread.start() -> (See: here for details)
## ParallelScavenge の場合: ### Minor GC の場合 -> instanceRefKlass::oop_push_contents() -> instanceRefKlass::specialized_oop_push_contents() -> ReferenceProcessor::discover_reference() -> -> ReferenceProcessor::get_discovered_list() -> ReferenceProcessor::add_to_discovered_list_mt() (<= マルチスレッドの場合は最後の処理はこの中で行われる) ### Major GC の場合 -> instanceRefKlass::oop_follow_contents() -> -> ReferenceProcessor::discover_reference() -> (同上) ## G1CollectedHeap の場合: ### Minor GC の場合 -> instanceRefKlass::oop_oop_iterate_backwards_v() または instanceRefKlass::oop_oop_iterate_backwards_nv() -> -> ReferenceProcessor::discover_reference() -> (同上) ### Major GC の場合 -> instanceRefKlass::oop_follow_contents() -> -> ReferenceProcessor::discover_reference() -> (同上) ## GenCollectedHeap の場合: ### Minor GC の場合 #### UseSerialGC の場合 -> instanceRefKlass::oop_oop_iterate() または instanceRefKlass::oop_oop_iterate_v() または instanceRefKlass::oop_oop_iterate_nv() -> -> ReferenceProcessor::discover_reference() -> (同上) ### Major GC の場合 #### UseSerialGC の場合 -> instanceRefKlass::oop_follow_contents() -> -> ReferenceProcessor::discover_reference() -> (同上)
-> ReferenceProcessor::setup_policy() -> ReferencePolicy::setup()
なお, ReferenceProcessor::process_discovered_references() は引数として以下の型のクロージャーを受け取る.
-> ReferenceProcessor::process_discovered_references() (1) soft reference に対して処理を行う. -> ReferenceProcessor::process_discovered_reflist() -> (1) リスト中から, 使用している ReferencePolicy オブジェクトによって消去しなくてよいと判断された soft reference を除外する (<= なお, この処理は soft reference の場合のみ実行される処理で有り, weak reference, final reference, phantom reference の場合には実行されない). -> ReferenceProcessor::process_phase1() (1) リスト中から, 差し先のオブジェクトがまだ生きている参照オブジェクトを除外する. -> ReferenceProcessor::process_phase2() (1) リストに残った参照オブジェクトおよびその差し先を live 状態にしておく (この先 ReferenceQueue に入れて処理される場合, この時点で死んでしまうとまずいので). (なお差し先については, dead にしてよいと指定されていた場合は live 状態にせず, 代わりに参照オブジェクトの差し先を示すフィールドを null にしておく) -> ReferenceProcessor::process_phase3() (1) weak reference に対して処理を行う. -> ReferenceProcessor::process_discovered_reflist() -> (同上) (1) final reference に対して処理を行う. -> ReferenceProcessor::process_discovered_reflist() -> (同上) (1) phantom reference に対して処理を行う. -> ReferenceProcessor::process_discovered_reflist() -> (同上) (1) Weak global JNI references に対して処理を行う. -> ReferenceProcessor::process_phaseJNI() -> JNIHandles::weak_oops_do() -> JNIHandleBlock::weak_oops_do() -> JvmtiExport::weak_oops_do() -> #TODO
-> ReferenceProcessor::enqueue_discovered_references() -> ReferenceProcessor::enqueue_discovered_ref_helper() -> ReferenceProcessor::enqueue_discovered_reflists() -> RefProcEnqueueTask::work() -> ReferenceProcessor::enqueue_discovered_reflist() -> ReferenceProcessor::enqueue_discovered_reflist()
See: here for details
See: here for details
See: here for details
See: here for details
(#Under Construction)
See: here for details
See: here for details
See: here for details
(#Under Construction)
See: here for details
See: here for details
See: here for details
See: here for details
See: here for details
See: here for details
See: here for details
See: here for details
See: here for details
See: here for details
See: here for details
See: here for details
See: here for details
See: here for details
See: here for details
(#Under Construction)
See: here for details
See: here for details
See: here for details
See: here for details
See: here for details
(#Under Construction)
See: here for details
See: here for details
See: here for details
See: here for details
(#Under Construction) See: here for details
See: here for details
See: here for details
See: here for details
(#Under Construction) See: here for details
See: here for details
See: here for details
This document is available under the GNU GENERAL PUBLIC LICENSE Version 2.