什么是垃圾收集根?它们是如何在 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 根列表看起来比我最初在第一句话中写的要大很多,所以我试着在下面列出它们,并附上一些评论:


问题:

虽然可以在源代码中找到很多东西,但我仍然很难理解其中的一些 GC 根,或者这些 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_doCLDToOopClosure的目的是标记通过方法指针引用的对象,否则未标记:

// 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"含义的算法和数据结构。例如。并发收集器可能会将所有在初始标记和最终注释之间修改的引用视为可达,即使它们不是。