什么是垃圾收集根?它们是如何在 HotSpot JVM 中找到的?
What exactly is considered a garbage collection root and how are they found in the HotSpot JVM?
简介:
在大学里,人们了解到 Java(和类似语言)中典型的垃圾收集根源是 静态变量 加载的 classes, 当前 运行 个线程的线程局部变量 ,"external references",例如 JNI 句柄 ,以及 GC 特定变量,例如 old-to-young 指针 在分代垃圾收集器的 Minor GC 期间。理论上,这听起来并不难。
问题:
我正在阅读 HotSpot source code 并且对如何在 VM 中检测到这些垃圾收集根很感兴趣,即在 JVM 源代码中使用了哪些方法访问所有根。
调查:
我找到了属于各种 GC 实现的各种文件(例如,psMarkSweep.cpp
),它们包含非常相似的结构。
以下是 psMarkSweep.cpp
中的方法 PSMarkSweep::mark_sweep_phase1
我认为涵盖了强大的根源:
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()));
来自 psScavenge.cpp
的以下代码似乎为不同类型的 GC 根添加了任务:
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));
查看 ScavangeRootsTask
,我们看到熟悉的代码,类似于 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);
}
洞察力:
源代码中的这个 GC 根列表看起来比我最初在第一句话中写的要大很多,所以我试着在下面列出它们,并附上一些评论:
- 宇宙:好的,宇宙。主要是某些 classes.
的镜像
- JNI 句柄:同样清楚,通过 JNI 创建的句柄使对象保持活动状态。
- Threads:这个访问 thead-local 根。但在这里我们看到了第一个区别。
psMarkSweep.cpp
使用 CLDToOopClosure 并对 Code blob 做一些事情,而 psScavange.cpp
则没有。 Afaik,CLD 代表 Class loader data,但我不知道为什么它在一种情况下使用,而在另一种情况下不使用。代码块也是如此。
- 对象同步器:用于同步的监视器。
- Flat Profiler:作为 Hotspot 一部分的分析器,保持其 class 加载程序。
- Management: 为某些管理服务保活的对象,如MemoryPoolMXBean等
- JVMTI: 保持 JVMTI 断点和 JVMTI 分配的对象。
- 系统字典:保持所有由Java系统class加载器加载的classes,以及引用的对象通过 classes 的静态字段。
- Class Loader Data Graph:这个我有点看不懂。我认为这是 class 加载器而不是 Java 系统 class 加载器,即,这涵盖了由不同加载器加载的 classes(及其静态字段) class 装载机?
- 代码缓存:代码缓存包含某些代码块,但我仍然不确定这些代码块到底是什么。似乎代码 blob 代表有关(已编译)代码帧的信息,我对此是否正确?但我仍然不明白为什么有时在遍历线程堆栈时访问这些代码块(如
psMarkSweep.cpp
中所做的),有时使用 CodeCache 访问(如 psScavenge.cpp
中所做的)。
- (Only for Minor GC) Old-to-young roots: Clear.
问题:
虽然可以在源代码中找到很多东西,但我仍然很难理解其中的一些 GC 根,或者这些 GC 根是如何找到的。
- 什么是代码块?它包含哪些 GC 根尚未通过访问带有 oop 闭包的线程覆盖?什么是代码缓存?
- 收集所有线程局部根:使用
CLDToOopClosure
和 MarkingCodeBlobClosure
结合 Threads::oops_do
有什么区别(如 psMarkSweep.cpp
中所做的) ,与使用 oop 闭包访问线程并另外执行 ClassLoaderDataGraph::oops_do
和 CodeCache::scavenge_root_nmethods_do
(由 psScavenge.cpp
使用)相比。
- 什么是classloader数据图(对比系统字典)?它是应用程序 class 加载器的集合吗?
- 驻留字符串如何在 GC 中存活下来?它们是否驻留在堆外的某个地方,垃圾收集不会影响它们?
- 其他垃圾收集器(例如 G1 GC)是否引入了新类型的根指针? (我认为不应该是这样)
备注:
我知道这是一个很长的问题,有很多子问题,但我认为很难将它分成多个子问题。
我感谢每一个发布的答案,即使它没有涵盖上面提出的所有问题的答案,即使是部分问题的答案也会对我有所帮助。谢谢!
由于这个post有几个问题,我敢回答其中的几个:
1) 代码块是 JIT 代码。它可能包含硬编码(作为汇编器立即数)对象指针(例如指向 class 对象,或指向静态最终)。如果移动对象,则调整代码中的立即数。
2) 不知道
3) class加载程序数据是本机元数据对象(不在堆内),可能包含(例如)对加载的 classes 的引用。
4) interned 字符串就像常规对象一样驻留在堆中(在 perm gen 的旧 VM 中)。唯一的区别是(出于驻留和缓存的原因)它们通常从不被收集并且总是隐式地活着。
5)据我所知,一个GC本身不应该引入一个新的GC Root类,毕竟一个GC Root作为一个概念就是GC-independent。但是,每个 GC 可能会以不同的方式存储和处理它们。
编辑:
刚想到别的事情:
2) VM 大量使用闭包,这基本上意味着虚拟调用。然而,虚拟调用可能很昂贵(尤其是当你经常这样做时,例如对堆中的每个对象和每个指针),因此 VM 不是组合现有的闭包,而是经常实现专门的闭包以避免不必要的虚拟调用。这可能是一个原因。
1') 我只是注意到有人可能会将我的回答解释为只是多余的根(因为 class 对象和静态决赛也总是从其他地方引用)。首先,从 GC 的角度来看,它们并不是多余的,因为如果对象被移动,立即数仍然必须进行调整。其次,JIT 可能决定硬编码指向任何对象的指针,例如,如果它在解释时检测到特定调用总是 returns 指向同一对象的指针。因此代码 blob 根可能是特定对象的唯一根。
您自己已经发现,<Subsystem>::oops_do()
是 HotSpot JVM 中访问 <Subsystem>
的 GC 根的典型机制。顺便说一句,很好的分析。只要继续浏览 VM 源代码,您就会找到答案,因为代码中有很多有用的注释。
请注意,oops_do
的目的不仅是标记可达对象,而且还处理引用本身,特别是在压缩期间重新定位它们。
CodeBlob 是一段生成的代码。它不仅涵盖 JITted 方法(又名 nmethods
),还涵盖运行时生成的各种 VM 存根和例程。
// CodeBlob - superclass for all entries in the CodeCache.
//
// Suptypes are:
// nmethod : Compiled Java methods (include method that calls to native code)
// RuntimeStub : Call to VM runtime methods
// DeoptimizationBlob : Used for deoptimizatation
// ExceptionBlob : Used for stack unrolling
// SafepointBlob : Used to handle illegal instruction exceptions
这些代码片段可能包含对堆对象的嵌入式引用,例如String/Class/MethodHandle 文字和静态最终常量。
Threads::oops_do
中CLDToOopClosure
的目的是标记通过方法指针引用的对象,否则未标记:
// The method pointer in the frame might be the only path to the method's
// klass, and the klass needs to be kept alive while executing. The GCs
// don't trace through method pointers, so typically in similar situations
// the mirror or the class loader of the klass are installed as a GC root.
// To minimze the overhead of doing that here, we ask the GC to pass down a
// closure that knows how to keep klasses alive given a ClassLoaderData.
cld_f->do_cld(m->method_holder()->class_loader_data());
类似地,MarkingCodeBlobClosure
用于标记仅从activenmethods:
引用的对象
// In cases where perm gen is collected, GC will want to mark
// oops referenced from nmethods active on thread stacks so as to
// prevent them from being collected. However, this visit should be
// restricted to certain phases of the collection only. The
// closure decides how it wants nmethods to be traced.
if (cf != NULL)
cf->do_code_blob(_cb);
请注意,在标记阶段不会调用 CodeCache::scavenge_root_nmethods_do
:
// 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));
SystemDictionary
主要负责解析class类的符号名。它不作为标记的 GC 根(Bootstrap 和系统 classloaders 除外)。另一方面,ClassLoaderDataGraph
维护 class 加载器实体的完整链接集。它确实充当GC根并负责class卸载。
// A ClassLoaderData identifies the full set of class types that a class
// loader's name resolution strategy produces for a given configuration of the
// class loader.
// Class types in the ClassLoaderData may be defined by from class file binaries
// provided by the class loader, or from other class loader it interacts with
// according to its name resolution strategy.
// ...
// ClassLoaderData carries information related to a linkset (e.g.,
// metaspace holding its klass definitions).
// The System Dictionary and related data structures (e.g., placeholder table,
// loader constraints table) as well as the runtime representation of classes
// only reference ClassLoaderData.
//
// Instances of java.lang.ClassLoader holds a pointer to a ClassLoaderData that
// that represent the loader's "linking domain" in the JVM.
驻留字符串不需要 就可以在 GC 中存活下来。它们不是 GC 根。卸载无法访问的驻留字符串是可以的,HotSpot 实际上就是这样做的。
垃圾收集器本身不会引入新的根类型,但它可能会使用影响"reachability"含义的算法和数据结构。例如。并发收集器可能会将所有在初始标记和最终注释之间修改的引用视为可达,即使它们不是。
简介:
在大学里,人们了解到 Java(和类似语言)中典型的垃圾收集根源是 静态变量 加载的 classes, 当前 运行 个线程的线程局部变量 ,"external references",例如 JNI 句柄 ,以及 GC 特定变量,例如 old-to-young 指针 在分代垃圾收集器的 Minor GC 期间。理论上,这听起来并不难。
问题:
我正在阅读 HotSpot source code 并且对如何在 VM 中检测到这些垃圾收集根很感兴趣,即在 JVM 源代码中使用了哪些方法访问所有根。
调查:
我找到了属于各种 GC 实现的各种文件(例如,psMarkSweep.cpp
),它们包含非常相似的结构。
以下是 psMarkSweep.cpp
中的方法 PSMarkSweep::mark_sweep_phase1
我认为涵盖了强大的根源:
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()));
来自 psScavenge.cpp
的以下代码似乎为不同类型的 GC 根添加了任务:
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));
查看 ScavangeRootsTask
,我们看到熟悉的代码,类似于 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);
}
洞察力:
源代码中的这个 GC 根列表看起来比我最初在第一句话中写的要大很多,所以我试着在下面列出它们,并附上一些评论:
- 宇宙:好的,宇宙。主要是某些 classes. 的镜像
- JNI 句柄:同样清楚,通过 JNI 创建的句柄使对象保持活动状态。
- Threads:这个访问 thead-local 根。但在这里我们看到了第一个区别。
psMarkSweep.cpp
使用 CLDToOopClosure 并对 Code blob 做一些事情,而psScavange.cpp
则没有。 Afaik,CLD 代表 Class loader data,但我不知道为什么它在一种情况下使用,而在另一种情况下不使用。代码块也是如此。 - 对象同步器:用于同步的监视器。
- Flat Profiler:作为 Hotspot 一部分的分析器,保持其 class 加载程序。
- Management: 为某些管理服务保活的对象,如MemoryPoolMXBean等
- JVMTI: 保持 JVMTI 断点和 JVMTI 分配的对象。
- 系统字典:保持所有由Java系统class加载器加载的classes,以及引用的对象通过 classes 的静态字段。
- Class Loader Data Graph:这个我有点看不懂。我认为这是 class 加载器而不是 Java 系统 class 加载器,即,这涵盖了由不同加载器加载的 classes(及其静态字段) class 装载机?
- 代码缓存:代码缓存包含某些代码块,但我仍然不确定这些代码块到底是什么。似乎代码 blob 代表有关(已编译)代码帧的信息,我对此是否正确?但我仍然不明白为什么有时在遍历线程堆栈时访问这些代码块(如
psMarkSweep.cpp
中所做的),有时使用 CodeCache 访问(如psScavenge.cpp
中所做的)。 - (Only for Minor GC) Old-to-young roots: Clear.
问题:
虽然可以在源代码中找到很多东西,但我仍然很难理解其中的一些 GC 根,或者这些 GC 根是如何找到的。
- 什么是代码块?它包含哪些 GC 根尚未通过访问带有 oop 闭包的线程覆盖?什么是代码缓存?
- 收集所有线程局部根:使用
CLDToOopClosure
和MarkingCodeBlobClosure
结合Threads::oops_do
有什么区别(如psMarkSweep.cpp
中所做的) ,与使用 oop 闭包访问线程并另外执行ClassLoaderDataGraph::oops_do
和CodeCache::scavenge_root_nmethods_do
(由psScavenge.cpp
使用)相比。 - 什么是classloader数据图(对比系统字典)?它是应用程序 class 加载器的集合吗?
- 驻留字符串如何在 GC 中存活下来?它们是否驻留在堆外的某个地方,垃圾收集不会影响它们?
- 其他垃圾收集器(例如 G1 GC)是否引入了新类型的根指针? (我认为不应该是这样)
备注:
我知道这是一个很长的问题,有很多子问题,但我认为很难将它分成多个子问题。 我感谢每一个发布的答案,即使它没有涵盖上面提出的所有问题的答案,即使是部分问题的答案也会对我有所帮助。谢谢!
由于这个post有几个问题,我敢回答其中的几个:
1) 代码块是 JIT 代码。它可能包含硬编码(作为汇编器立即数)对象指针(例如指向 class 对象,或指向静态最终)。如果移动对象,则调整代码中的立即数。
2) 不知道
3) class加载程序数据是本机元数据对象(不在堆内),可能包含(例如)对加载的 classes 的引用。
4) interned 字符串就像常规对象一样驻留在堆中(在 perm gen 的旧 VM 中)。唯一的区别是(出于驻留和缓存的原因)它们通常从不被收集并且总是隐式地活着。 5)据我所知,一个GC本身不应该引入一个新的GC Root类,毕竟一个GC Root作为一个概念就是GC-independent。但是,每个 GC 可能会以不同的方式存储和处理它们。
编辑:
刚想到别的事情:
2) VM 大量使用闭包,这基本上意味着虚拟调用。然而,虚拟调用可能很昂贵(尤其是当你经常这样做时,例如对堆中的每个对象和每个指针),因此 VM 不是组合现有的闭包,而是经常实现专门的闭包以避免不必要的虚拟调用。这可能是一个原因。
1') 我只是注意到有人可能会将我的回答解释为只是多余的根(因为 class 对象和静态决赛也总是从其他地方引用)。首先,从 GC 的角度来看,它们并不是多余的,因为如果对象被移动,立即数仍然必须进行调整。其次,JIT 可能决定硬编码指向任何对象的指针,例如,如果它在解释时检测到特定调用总是 returns 指向同一对象的指针。因此代码 blob 根可能是特定对象的唯一根。
您自己已经发现,<Subsystem>::oops_do()
是 HotSpot JVM 中访问 <Subsystem>
的 GC 根的典型机制。顺便说一句,很好的分析。只要继续浏览 VM 源代码,您就会找到答案,因为代码中有很多有用的注释。
请注意,oops_do
的目的不仅是标记可达对象,而且还处理引用本身,特别是在压缩期间重新定位它们。
CodeBlob 是一段生成的代码。它不仅涵盖 JITted 方法(又名 nmethods
),还涵盖运行时生成的各种 VM 存根和例程。
// CodeBlob - superclass for all entries in the CodeCache.
//
// Suptypes are:
// nmethod : Compiled Java methods (include method that calls to native code)
// RuntimeStub : Call to VM runtime methods
// DeoptimizationBlob : Used for deoptimizatation
// ExceptionBlob : Used for stack unrolling
// SafepointBlob : Used to handle illegal instruction exceptions
这些代码片段可能包含对堆对象的嵌入式引用,例如String/Class/MethodHandle 文字和静态最终常量。
Threads::oops_do
中CLDToOopClosure
的目的是标记通过方法指针引用的对象,否则未标记:
// The method pointer in the frame might be the only path to the method's
// klass, and the klass needs to be kept alive while executing. The GCs
// don't trace through method pointers, so typically in similar situations
// the mirror or the class loader of the klass are installed as a GC root.
// To minimze the overhead of doing that here, we ask the GC to pass down a
// closure that knows how to keep klasses alive given a ClassLoaderData.
cld_f->do_cld(m->method_holder()->class_loader_data());
类似地,MarkingCodeBlobClosure
用于标记仅从activenmethods:
// In cases where perm gen is collected, GC will want to mark
// oops referenced from nmethods active on thread stacks so as to
// prevent them from being collected. However, this visit should be
// restricted to certain phases of the collection only. The
// closure decides how it wants nmethods to be traced.
if (cf != NULL)
cf->do_code_blob(_cb);
请注意,在标记阶段不会调用 CodeCache::scavenge_root_nmethods_do
:
// 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));
SystemDictionary
主要负责解析class类的符号名。它不作为标记的 GC 根(Bootstrap 和系统 classloaders 除外)。另一方面,ClassLoaderDataGraph
维护 class 加载器实体的完整链接集。它确实充当GC根并负责class卸载。
// A ClassLoaderData identifies the full set of class types that a class
// loader's name resolution strategy produces for a given configuration of the
// class loader.
// Class types in the ClassLoaderData may be defined by from class file binaries
// provided by the class loader, or from other class loader it interacts with
// according to its name resolution strategy.
// ...
// ClassLoaderData carries information related to a linkset (e.g.,
// metaspace holding its klass definitions).
// The System Dictionary and related data structures (e.g., placeholder table,
// loader constraints table) as well as the runtime representation of classes
// only reference ClassLoaderData.
//
// Instances of java.lang.ClassLoader holds a pointer to a ClassLoaderData that
// that represent the loader's "linking domain" in the JVM.
驻留字符串不需要 就可以在 GC 中存活下来。它们不是 GC 根。卸载无法访问的驻留字符串是可以的,HotSpot 实际上就是这样做的。
垃圾收集器本身不会引入新的根类型,但它可能会使用影响"reachability"含义的算法和数据结构。例如。并发收集器可能会将所有在初始标记和最终注释之间修改的引用视为可达,即使它们不是。