Up Top

Memory allocation (& GC 処理) : メモリの確保処理 (GC 処理) : 参照オブジェクト (java.lang.ref オブジェクト) の GC 処理


概要(Summary)

参照オブジェクト(java.lang.ref.Reference) の処理は 2段階からなる.

  1. まず, GC 中に各参照オブジェクトの差し先が死んでいるかどうかの判定が行われる.

差し先のオブジェクトが死んでいた場合, その死んでいるオブジェクトは回収される.

さらに, それを指していた参照オブジェクト自体も (ReferenceQueue に登録されていた場合には) pending list につながれる.

  1. 次に, Reference Handler スレッドが pending list の処理を行う.

これにより, pending list 中のオブジェクトが対応する java.lang.ref.ReferenceQueue オブジェクトにプッシュされる.

1. GC 時の処理

GC 時には, まず普通の参照を辿る処理が行われ, その後に参照オブジェクトだけを改めて辿る処理が行われる (See: here for details).

これは, 普通の参照を辿り終わってからでないと, 参照オブジェクトの指し先が live かを判定できないため (例えば, soft reference の指し先は, 強参照から指されていなければ dead になる).

また, 同様の理由から, 参照オブジェクトの処理も強い順に処理が行われる.

  1. soft reference
  2. weak reference
  3. phantom reference

これらの処理は, 参照を辿る処理の途中で参照オブジェクトを見つけると, 見つけた参照オブジェクトをリスト上につないでいくことで実現されている. 参照を辿り終わった後でリストを辿っていけば, 見つけた参照オブジェクトを全て処理できる.

なお, このリストは参照オブジェクトの discovered フィールドを使って構築される. また, 参照オブジェクトの種類毎に異なるリストが使われる (これにより参照オブジェクトの処理時に参照の強い順に処理するのが簡単になっている). さらに, マルチスレッドで処理できるようスレッド毎にリストが用意されている. このため「参照オブジェクトの種別×スレッド数」分だけのリストがある.

これらのリストの先頭は ReferenceProcessor オブジェクト内の以下のフィールドになっている (これらのフィールド自体は DiscoveredList クラスの配列となっている. 配列となっているのは, スレッドの数分だけ存在するため. 下の表記では "[id]" というのがスレッド数だけあることを示す. DiscoveredList という名前だが, 実際にはこれはリストの先頭になるだけで, そこから先はつながっている参照オブジェクト自身の discovered フィールドを使って中身をつないでいる)

これらの処理が終わった後, ...

2. Reference Handler の処理

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;

備考(Notes)

java.lang.ref.Reference の内部実装について

java.lang.ref.Reference オブジェクトは, 以下のように状態遷移する.

  Active ----> Pending  ----> Enqueued ----.
    |                                      |
    |                                      V
    -----------------------------------> Inactive

現在どの状態にあるかは queue フィールドと next フィールドを見れば分かるようになっている.

なお 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.
         */

lock フィールド

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();

処理の流れ (概要)(Execution Flows : Summary)

初期化処理

ReferenceProcessor の初期化処理

(HotSpot の起動時処理) (See: here for details)
-> Threads::create_vm()
   -> init_globals()
      -> referenceProcessor_init()
         -> ReferenceProcessor::init_statics()

Reference Handler スレッドの生成処理

-> java.lang.ref.Reference クラスの static ブロックでの処理
   -> java.lang.ref.Reference$ReferenceHandler.<init>()
   -> java.lang.Thread.start()
      -> (See: here for details)

GC 処理時の参照オブジェクト処理の流れ

  1. まず, 通常の参照(強参照)を辿る処理が行われる (この処理の詳細はそれぞれの GC アルゴリズム毎に異なる). この処理中で見つかった参照オブジェクトは ReferenceProcessor オブジェクトのリストに登録されていく. (この登録処理は ReferenceProcessor::discover_reference() で行う)
  ## 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()
           -> (同上)
  1. 強参照を全て辿り終わったら, 参照オブジェクトの処理が始まる. まず ReferenceProcessor オブジェクトの初期化処理を行う.
  -> ReferenceProcessor::setup_policy()
     -> ReferencePolicy::setup()
  1. 実際の参照オブジェクトの処理は ReferenceProcessor::process_discovered_references() で行う. この処理で, 差し先が生きている参照オブジェクトについてはリストから除外される.

なお, 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
  1. リストに残った参照オブジェクト(= 差し先が死んだため特殊な処理が必要な参照オブジェクト)については, ReferenceProcessor::enqueue_discovered_references() で pending list に追加する.
  -> ReferenceProcessor::enqueue_discovered_references()
     -> ReferenceProcessor::enqueue_discovered_ref_helper()
        -> ReferenceProcessor::enqueue_discovered_reflists()
           ->
              RefProcEnqueueTask::work()
              -> ReferenceProcessor::enqueue_discovered_reflist()
           -> ReferenceProcessor::enqueue_discovered_reflist()

GC 処理後の参照オブジェクト処理

  1. Reference Handler によって pending list 内の参照オブジェクトが取り出され, 対応する ReferenceQueue へとプッシュされる.

処理の流れ (詳細)(Execution Flows : Details)

referenceProcessor_init()

See: here for details

ReferenceProcessor::init_statics()

See: here for details

java.lang.ref.Reference クラスの static ブロック

See: here for details

instanceRefKlass::oop_push_contents()

See: here for details

instanceRefKlass::specialized_oop_push_contents()

(#Under Construction)

ReferenceProcessor::discover_reference()

See: here for details

ReferenceProcessor::get_discovered_list()

See: here for details

ReferenceProcessor::add_to_discovered_list_mt()

See: here for details

instanceRefKlass::oop_follow_contents()

(#Under Construction)

ReferenceProcessor::setup_policy()

See: here for details

ReferencePolicy::setup()

See: here for details

LRUCurrentHeapPolicy::setup()

See: here for details

LRUMaxHeapPolicy::setup()

See: here for details

ReferenceProcessor::process_discovered_references()

See: here for details

ReferenceProcessor::update_soft_ref_master_clock()

See: here for details

ReferenceProcessor::balance_queues()

See: here for details

ReferenceProcessor::process_discovered_reflist()

See: here for details

RefProcPhase1Task::work()

See: here for details

ReferenceProcessor::process_phase1()

See: here for details

DiscoveredListIterator::make_active()

See: here for details

DiscoveredListIterator::make_referent_alive()

See: here for details

RefProcPhase2Task::work()

See: here for details

ReferenceProcessor::process_phase2()

See: here for details

ReferenceProcessor::pp2_work()

See: here for details

ReferenceProcessor::pp2_work_concurrent_discovery()

(#Under Construction)

RefProcPhase3Task::work()

See: here for details

ReferenceProcessor::process_phase3()

See: here for details

DiscoveredListIterator::update_discovered()

See: here for details

DiscoveredListIterator::clear_referent()

See: here for details

AbstractRefProcTaskExecutor::set_single_threaded_mode()

See: here for details

ParNewRefProcTaskExecutor::set_single_threaded_mode()

(#Under Construction)

ReferenceProcessor::process_phaseJNI()

See: here for details

JNIHandles::weak_oops_do()

See: here for details

JNIHandleBlock::weak_oops_do()

See: here for details

JvmtiExport::weak_oops_do()

See: here for details

JvmtiTagMap::weak_oops_do()

(#Under Construction) See: here for details

ReferenceProcessor::enqueue_discovered_references()

See: here for details

ReferenceProcessor::enqueue_discovered_ref_helper()

See: here for details

ReferenceProcessor::enqueue_discovered_reflists()

See: here for details

RefProcEnqueueTask::work()

(#Under Construction) See: here for details

ReferenceProcessor::enqueue_discovered_reflist()

See: here for details

java.lang.ref.Reference$ReferenceHandler.run()

See: here for details


This document is available under the GNU GENERAL PUBLIC LICENSE Version 2.