究竟什么是垃圾回收根,如何在 HotSpot JVM 中找到它们?[已关闭]
介绍:
在大学里,人们了解到Java(和类似语言)中典型的垃圾回收根是加载类的静态变量,当前运行的线程的线程局部变量,“外部引用”(如JNI句柄)和GC特定属性,例如代际垃圾回收器的次要GC期间的旧到年轻指针。从理论上讲,这听起来并不难。
问题:
我正在阅读HotSpot源代码,并对如何在VM内部检测这些垃圾回收根感兴趣,即在JVM源代码中使用哪些方法来访问所有根。
调查:
我发现各种文件(例如,psMarkSweep.cpp
),属于各种GC实现,包含非常相似的结构。
以下是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根列表看起来比我最初在第一句话中写的要大得多,所以我试着在下面列出它们,并附上一些注释:
- 宇宙:好的,宇宙。主要是某些类的镜像。
- JNI句柄:同样清晰,通过JNI创建的句柄使对象保持活动状态。
-
线程:这个访问了thead-local根。但在这里,我们看到了第一个区别。 使用 CLDToOopClosure 并对代码 blob 执行某些操作,而不执行。Afaik,CLD代表类加载器数据,但我不知道为什么在一种情况下使用它,而在另一种情况下却不使用它。代码 blob 的帐户相同。
psMarkSweep.cpp
psScavange.cpp
- 对象同步器:用于同步的监视器。
- 平面探查器:作为 Hotspot 一部分的探查器,可保持其类装入器处于活动状态。
- 管理:为某些管理服务保持活动状态的对象,例如 MemoryPoolMXBean 等。
- JVMTI:保持 JVMTI 分配的 JVMTI 断点和对象处于活动状态。
- 系统字典:使 Java 系统类装入器加载的所有类保持活动状态,从而使类的静态字段引用的对象保持活动状态。
- 类加载器数据图:这个对我来说有点不清楚。我认为这是不是Java系统类装入器的类装入器,也就是说,这涵盖了由不同类装入器装入的类(及其静态字段)?
-
代码缓存:代码缓存包含某些代码 blob,但我仍然不确定这些代码 blob 到底是什么。似乎代码 blob 表示有关(已编译的)代码帧的信息,我对此是否正确?但我仍然不明白为什么在遍历线程堆栈时有时会访问这些代码 blob(如 中所做的那样),有时使用 CodeCache 访问这些代码 blob(如 中所做的那样)。
psMarkSweep.cpp
psScavenge.cpp
- (仅适用于次要GC)从老到年轻的根:清除。
问题:
虽然在源代码中可以找到很多东西,但我仍然很难理解其中一些GC根,或者这些GC根是如何找到的。
- 什么是代码 blob?它包含哪些 GC 根,这些根尚未通过访问具有 oop 闭包的线程来覆盖?什么是代码缓存?
- 收集所有线程本地根:将 a 和 a 组合使用(如 中所示)与访问具有 oop 闭包并附加执行 和 (如 所用)的线程相比,有什么区别。
CLDToOopClosure
MarkingCodeBlobClosure
Threads::oops_do
psMarkSweep.cpp
ClassLoaderDataGraph::oops_do
CodeCache::scavenge_root_nmethods_do
psScavenge.cpp
- 什么是类装入器数据图(与系统字典相比)?它是应用程序类装入器的集合吗?
- 那么实习字符串呢,它们如何在GC中生存?它们是否位于堆外的某个位置,垃圾回收不会影响它们?
- 其他垃圾回收器(例如 G1 GC)是否引入了新型根指针?(我不认为情况应该如此)
备注:
我知道这是一个很长的问题,有各种子问题,但我认为很难把它分成多个问题。我很感激每一个发布的答案,即使它没有涵盖上面提出的所有问题的答案,即使部分问题的答案也会对我有所帮助。谢谢!