究竟什么被认为是垃圾收集根以及它们如何在HotSpot JVM中找到?

Mar*_*ger 16 java garbage-collection jvm heap-memory jvm-hotspot

介绍:

在大学里,人们了解到Java(和类似语言)中的典型垃圾收集根是加载类的静态变量,当前运行线程的线程局部变量,"外部引用"(如JNI句柄)和GC特定(如old-to) -在世代垃圾收集器的Minor GCs期间的年轻指针.从理论上讲,这听起来并不难.


问题:

我读的热点的源代码和感兴趣的是如何在VM内检测到这些垃圾收集根,即,该方法是使用内部的JVM源代码访问所有的根源.


调查:

我发现psMarkSweep.cpp属于各种GC实现的各种文件(例如)包含非常相似的结构.

以下是我认为涵盖强大根源的方法PSMarkSweep::mark_sweep_phase1方法psMarkSweep.cpp:

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()));    
Run Code Online (Sandbox Code Playgroud)

以下代码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));
Run Code Online (Sandbox Code Playgroud)

看一下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);
}
Run Code Online (Sandbox Code Playgroud)

洞察:

源代码中的GC根源列表看起来比我在第一句中写的要大得多,所以我尝试在下面列出它们,并附上一些注释:

  • 宇宙:好的,宇宙.主要是某些类的镜像.
  • JNI处理:同样清楚,通过JNI创建的句柄使对象保持活动状态.
  • 主题:这个访问thead-local根.但在这里我们看到了第一个区别.psMarkSweep.cpp使用CLDToOopClosure并使用Code blob执行某些操作,psScavange.cpp而不使用.Afaik,CLD代表类加载器数据,但我不知道为什么它在一种情况下使用,而在另一种情况下不使用.代码blob的相同帐户.
  • 对象同步器:用于同步的监视器.
  • Flat Profiler:作为Hotspot一部分的探查器,保持其类加载器的活力.
  • 管理:为某些管理服务保持活动的对象,例如MemoryPoolMXBean等.
  • JVMTI:保持JVMTI分配的JVMTI断点和对象.
  • 系统字典:保持Java系统类加载器加载的所有类,以及类的静态字段引用的对象.
  • 类加载器数据图:这个对我来说有点不清楚.我认为这是不是Java系统类加载器的类加载器,即,它包含由不同类加载器加载的类(及其静态字段)?
  • 代码缓存:代码缓存包含某些代码blob,但我仍然不确定这些代码blob到底是什么.似乎代码blob代表有关(编译)代码帧的信息,我对此是正确的吗?但是我仍然不明白为什么有时会在遍历线程堆栈时访问这些代码blob(如所做的那样psMarkSweep.cpp),有时使用CodeCache访问这些代码blob (如同psScavenge.cpp).
  • (仅适用于次要GC)从古到少的根源:清楚.

问题:

虽然在源代码中可以找到很多东西,但我仍然很难理解这些GC根源,或者如何找到这些GC根.

  • 什么是代码blob?通过访问带有oop闭包的线程,它包含哪些GC根目录?什么是代码缓存?
  • 收集所有线程本地根:与使用oop闭包访问线程并另外执行和(如所使用的)相比,使用a CLDToOopClosure和a MarkingCodeBlobClosure组合Threads::oops_do(如所做的psMarkSweep.cpp)有什么区别.ClassLoaderDataGraph::oops_doCodeCache::scavenge_root_nmethods_dopsScavenge.cpp
  • 什么是类加载器数据图(与系统字典相比)?它是应用程序类加载器的集合吗?
  • 那些实习字符串怎么样,他们如何在GC中生存?它们是否位于堆外部,垃圾收集不会影响它们?
  • 其他垃圾收集器,例如G1 GC,是否会引入新类型的根指针?(我不认为应该是这种情况)

备注:

我知道这是一个很长的问题,有各种各样的子问题,但我认为很难将它分成多个.我感谢每一个发布的答案,即使它没有涵盖上述所有问题的答案,即使答案中的部分问题也会对我有所帮助.谢谢!

apa*_*gin 9

正如您自己已经发现的那样,<Subsystem>::oops_do()HotSpot JVM中的典型机制是访问GC的根源<Subsystem>.顺便说一下,好的分析.只需继续浏览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
Run Code Online (Sandbox Code Playgroud)

这些代码段可能包含对Heap对象的嵌入式引用,例如String/Class/MethodHandle文字和静态最终常量.


CLDToOopClosurein 的目的Threads::oops_do是标记通过方法指针引用的对象,否则不标记:

// 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());
Run Code Online (Sandbox Code Playgroud)

同样,MarkingCodeBlobClosure用于标记仅从活动 nmethods 引用的对象:

// 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);
Run Code Online (Sandbox Code Playgroud)

请注意,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));
Run Code Online (Sandbox Code Playgroud)

SystemDictionary主要负责解析类的符号名称.它不用作标记的GC根(Bootstrap和System类加载器除外).另一方面,ClassLoaderDataGraph维护类加载器实体的完整链接集.它确实充当GC根,负责类卸载.

// 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.
Run Code Online (Sandbox Code Playgroud)

实习字符串不需要在GC中存活.它们不是GC的根源.可以卸载其他无法访问的实习字符串,而HotSpot实际上就是这样做的.


垃圾收集器本身不会引入新类型的根,但它可能会使用影响"可达性"含义的算法和数据结构.例如,并发收集器可以将在初始标记和最终备注之间修改的所有引用视为可达,即使它们不可达.