Intro:
At university one learns that typical garbage collection roots in Java (and similar languages) are static variables of loaded classes, thread-local variables of currently running threads, "external references" such as JNI handles, and GC-specifics such as old-to-young pointers during Minor GCs of a generational garbage collector. In theory, this does not sound that hard.
Problem:
I am reading the HotSpot source code and was interested in how these garbage collection roots are detected inside the VM, i.e., which methods are used inside the JVM source code to visit all roots.
Investigation:
I found various files (e.g., psMarkSweep.cpp
), belonging to various GC implementations, that contain very similar constructs.
Following is the method PSMarkSweep::mark_sweep_phase1
method from psMarkSweep.cpp
that I think covers the strong roots:
ParallelScavengeHeap::ParStrongRootsScope psrs;
Universe::oops_do(mark_and_push_closure());
JNIHandles::oops_do(mark_and_push_closure()); // Global (strong) JNI handles
CLDToOopClosure mark_and_push_from_cld(mark_and_push_closure());
MarkingCodeBlobClosure each_active_code_blob(mark_and_push_closure(), !CodeBlobToOopClosure::FixRelocations);
Threads::oops_do(mark_and_push_closure(), &mark_and_push_from_cld, &each_active_code_blob);
ObjectSynchronizer::oops_do(mark_and_push_closure());
FlatProfiler::oops_do(mark_and_push_closure());
Management::oops_do(mark_and_push_closure());
JvmtiExport::oops_do(mark_and_push_closure());
SystemDictionary::always_strong_oops_do(mark_and_push_closure());
ClassLoaderDataGraph::always_strong_cld_do(follow_cld_closure());
// Do not treat nmethods as strong roots for mark/sweep, since we can unload them.
//CodeCache::scavenge_root_nmethods_do(CodeBlobToOopClosure(mark_and_push_closure()));
The following code from psScavenge.cpp
seems to add tasks for the different types of GC roots:
if (!old_gen->object_space()->is_empty()) {
// There are only old-to-young pointers if there are objects
// in the old gen.
uint stripe_total = active_workers;
for(uint i=0; i < stripe_total; i++) {
q->enqueue(new OldToYoungRootsTask(old_gen, old_top, i, stripe_total));
}
}
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::universe));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::jni_handles));
// We scan the thread roots in parallel
Threads::create_thread_roots_tasks(q);
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::object_synchronizer));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::flat_profiler));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::management));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::system_dictionary));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::class_loader_data));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::jvmti));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::code_cache));
Looking at ScavangeRootsTask
, we see familiar code similar to the code in psMarkSweep
:
void ScavengeRootsTask::do_it(GCTaskManager* manager, uint which) {
assert(Universe::heap()->is_gc_active(), "called outside gc");
PSPromotionManager* pm = PSPromotionManager::gc_thread_promotion_manager(which);
PSScavengeRootsClosure roots_closure(pm);
PSPromoteRootsClosure roots_to_old_closure(pm);
switch (_root_type) {
case universe:
Universe::oops_do(&roots_closure);
break;
case jni_handles:
JNIHandles::oops_do(&roots_closure);
break;
case threads:
{
ResourceMark rm;
CLDClosure* cld_closure = NULL; // Not needed. All CLDs are already visited.
Threads::oops_do(&roots_closure, cld_closure, NULL);
}
break;
case object_synchronizer:
ObjectSynchronizer::oops_do(&roots_closure);
break;
case flat_profiler:
FlatProfiler::oops_do(&roots_closure);
break;
case system_dictionary:
SystemDictionary::oops_do(&roots_closure);
break;
case class_loader_data:
{
PSScavengeKlassClosure klass_closure(pm);
ClassLoaderDataGraph::oops_do(&roots_closure, &klass_closure, false);
}
break;
case management:
Management::oops_do(&roots_closure);
break;
case jvmti:
JvmtiExport::oops_do(&roots_closure);
break;
case code_cache:
{
MarkingCodeBlobClosure each_scavengable_code_blob(&roots_to_old_closure, CodeBlobToOopClosure::FixRelocations);
CodeCache::scavenge_root_nmethods_do(&each_scavengable_code_blob);
}
break;
default:
fatal("Unknown root type");
}
// Do the real work
pm->drain_stacks(false);
}
Insight:
This lists of GC roots in the source code look quite a bit larger than what I initially wrote in my first sentence, so I try to list them below, with some comments:
- Universe: Okay, the universe. Mostly mirrors of certain classes.
- JNI handles: Also clear, handles created via JNI that keep objects alive.
- Threads: This one visits the thead-local roots. But here we see the first difference.
psMarkSweep.cpp
uses a CLDToOopClosure and does something with Code blobs, whilepsScavange.cpp
does not. Afaik, CLD stands for Class loader data, but I don't know why it is used in one case, but not in another. The same accounts for code blobs. - Object Synchronizer: Monitors used for synchronization.
- Flat Profiler: A profiler that is part of Hotspot, keeps alive its class loader.
- Management: Objects that are kept alive for certain management services, such as MemoryPoolMXBean etc.
- JVMTI: Keeps alive JVMTI breakpoints and objects allocated by JVMTI.
- System Dictionary: Keeps alive all classes that are loaded by the Java system classloader, and thus the objects referenced by the classes' static fields.
- Class Loader Data Graph: This one is a bit unclear to me. I think this are the class loaders that are not the Java system classloader, i.e., this covers classes (and their static fields) that have been loaded by different class loaders?
- Code Cache: The code cache contains certain code blobs, but I am still not sure what exactly these code blobs are. It seems a code blob represent information about (compiled) code frames, am I right about this? But I still do not understand why these code blobs are sometimes visited while traversing the thread stacks (as done in
psMarkSweep.cpp
) and sometimes visited using the CodeCache (as done inpsScavenge.cpp
). - (Only for Minor GC) Old-to-young roots: Clear.
Questions:
While many things can be found in the source code, I still struggle to understand some of these GC roots, or how these GC roots are found.
- What is a code blob? What GC roots does it contain that are not already covered by visiting a thread with an oop closure? What is the code cache?
- To gather all thread-local roots: What is the difference between using a
CLDToOopClosure
and aMarkingCodeBlobClosure
in combination withThreads::oops_do
(as done inpsMarkSweep.cpp
), compared to visiting the threads with an oop closure and additionally executingClassLoaderDataGraph::oops_do
andCodeCache::scavenge_root_nmethods_do
(as used bypsScavenge.cpp
). - What is the class loader data graph (compared to the system dictionary)? Is it the collection of the applications class loaders?
- What about interned strings, how do they survive GCs? Do they reside somewhere outside the heap, where garbage collection does not affect them?
- Do other garbage collectors, e.g., the G1 GC, introduce new types of root pointers? (I don't think that this should be the case)
Remark:
I know this is a veeeery long question with various subquestions, but I think it would have been hard to split it into multiple ones. I appreciate every posted answer, even if it does not cover an answer to all the questions asked above, even answers to parts of them will help me. Thanks!