Runtime - AppCDS


1. 概念与流程

CDS介绍

传统CDS[0]分为Dump和Use两个大阶段:

-Xshare:off -XX:DumpLoadedClassList=test.log

-Xshare:dump -XX:SharedClassListFile=test.log -XX:SharedArchiveFile=test.jsa

-Xshare:on -XX:SharedArchiveFile=test.jsa

实际上为了使用CDS,一般还是需要三步。第一步产生一个包含类名字的列表test.log,第二步根据类名字列表产生CDS archive,第三步使用CDS Archive。可以搜索DumpSharedSpaces和UseSharedSpaces两个flag来找到后面两步的代码和逻辑。

CDS Dump完整流程位于MetaspaceShared::preload_and_dump,它先做一些初始化工作,比如读取classlist的类,并预加载,然后VMThread::execute一个VM_PopulateDumpSharedSpace做实际dump archive工作:

void MetaspaceShared::preload_and_dump(TRAPS) {
  { TraceTime timer("Dump Shared Spaces", TRACETIME_LOG(Info, startuptime));
    ResourceMark rm;
    char class_list_path_str[JVM_MAXPATHLEN];
    // Preload classes to be shared.
    // Should use some os:: method rather than fopen() here. aB.
    const char* class_list_path;
    if (SharedClassListFile == NULL) {
      // Construct the path to the class list (in jre/lib)
      // Walk up two directories from the location of the VM and
      // optionally tack on "lib" (depending on platform)
      os::jvm_path(class_list_path_str, sizeof(class_list_path_str));
      for (int i = 0; i < 3; i++) {
        char *end = strrchr(class_list_path_str, *os::file_separator());
        if (end != NULL) *end = '\0';
      }
      int class_list_path_len = (int)strlen(class_list_path_str);
      if (class_list_path_len >= 3) {
        if (strcmp(class_list_path_str + class_list_path_len - 3, "lib") != 0) {
          if (class_list_path_len < JVM_MAXPATHLEN - 4) {
            jio_snprintf(class_list_path_str + class_list_path_len,
                         sizeof(class_list_path_str) - class_list_path_len,
                         "%slib", os::file_separator());
            class_list_path_len += 4;
          }
        }
      }
      if (class_list_path_len < JVM_MAXPATHLEN - 10) {
        jio_snprintf(class_list_path_str + class_list_path_len,
                     sizeof(class_list_path_str) - class_list_path_len,
                     "%sclasslist", os::file_separator());
      }
      class_list_path = class_list_path_str;
    } else {
      class_list_path = SharedClassListFile;
    }

    tty->print_cr("Loading classes to share ...");
    _has_error_classes = false;
    int class_count = preload_classes(class_list_path, THREAD);
    if (ExtraSharedClassListFile) {
      class_count += preload_classes(ExtraSharedClassListFile, THREAD);
    }
    tty->print_cr("Loading classes to share: done.");

    log_info(cds)("Shared spaces: preloaded %d classes", class_count);

    // Rewrite and link classes
    tty->print_cr("Rewriting and linking classes ...");

    // Link any classes which got missed. This would happen if we have loaded classes that
    // were not explicitly specified in the classlist. E.g., if an interface implemented by class K
    // fails verification, all other interfaces that were not specified in the classlist but
    // are implemented by K are not verified.
    link_and_cleanup_shared_classes(CATCH);
    tty->print_cr("Rewriting and linking classes: done");

    SystemDictionary::clear_invoke_method_table();
    HeapShared::init_archivable_static_fields(THREAD);

    VM_PopulateDumpSharedSpace op;
    VMThread::execute(&op);
  }
}

以上是CDS Dump的大流程。JDK16上,JDK-8253909可以输出详细的cds archive文件内容,有助于debug。

(info = high level layout -- regions, etc)
java -Xshare:dump -Xlog:cds+map=info:file=cds.map:none:filesize=0

(debug = information about all metaspace objects)
java -Xshare:dump -Xlog:cds+map=debug:file=cds.map:none:filesize=0

(trace = values of every byte in the archive)
java -Xshare:dump -Xlog:cds+map=trace:file=cds.map:none:filesize=0

2. CDS Shared Archive

cds shared archive有五个区域

  • mc - misc code (method entry trampoline code)
  • rw - read-write metadata
  • ro - read-only space
  • md - misc data (c++ vtable)
  • od - optional data (original class file)

这五个区域按顺序分配,base addr可以通过-XX:SharedBaseAddress指定。分配过程则是在jvm启动的时候,初始化Metaspace::global_initialize中完成的。一个一个来。

2.1 mc - method entry

模板解释器在jvm创建的时候会为每种方法类型(zerolocal,synchronized,native...)生成entry(TemplateInterpreterGenerator::generate_all)。在开启appcds的情况下,还会多一个(update_cds_entry_table)步骤,即为cds_entry_table设置入口:

也就是说,模板解释器generate_all完之后,存在两张table,一张正常的_entry_table,一张_cds_entry_table,其中cds_entry_table的每个entry指向一段trampoline代码,这段代码跳到正常的entry_table的entry。

那么到底该用哪张表呢?这一步由Method::link_method决定:

void Method::link_method(TRAPS){
  if(is_shared(){
    use_cds_entry_table();
  }else{
  	use_entry_table();
  }
  ...
}

这一步还有一个疑问,它只为cds_entry_table设置了对应的cds entry,但是没有为_deopt_table,_safept_table, exception_entries这些设置?

2.2 rw和ro regions

当迭代下面roots的时候,jvm会区分哪些是rw数据,哪些是ros数据,然后分别写入各自的区域

static void iterate_roots(MetaspaceClosure* it) {
    GrowableArray* symbols = _ssc->get_sorted_symbols();
    for (int i=0; ilength(); i++) {
      it->push(symbols->adr_at(i));
    }
    if (_global_klass_objects != NULL) {
      for (int i = 0; i < _global_klass_objects->length(); i++) {
        it->push(_global_klass_objects->adr_at(i));
      }
    }
    FileMapInfo::metaspace_pointers_do(it);
    SystemDictionary::classes_do(it);
    if (NotFoundClassOpt && SystemDictionary::not_found_class_table()) {
      SystemDictionary::not_found_class_table()->metaspace_pointers_do(it);
    }
    Universe::metaspace_pointers_do(it);
    SymbolTable::metaspace_pointers_do(it);
    vmSymbols::metaspace_pointers_do(it);
  }

上面这些root中,在metaspace存放的数据大部分都是rw,其他都是ro。

2.3 md - cpp vtable

[0] https://openjdk.java.net/jeps/310
[1] http://cr.openjdk.java.net/~jiangli/Leyden/Java%20Class%20Pre-resolution%20and%20Pre-initialization%20(OpenJDK).pdf
[2] http://cr.openjdk.java.net/~jiangli/Leyden/Selectively%20Pre-initializing%20and%20Preserving%20Java%20Classes%20(OpenJDK).pdf

3. code insight

下面是一个标准路径,也就是ClassLoader.loadClass的过程中检查CDS archive看是否有klass
// [0] SystemDictionaryShared::find_or_load_shared_class()
// [1] JVM_FindLoadedClass
// [2] java.lang.ClassLoader.findLoadedClass0()
// [3] java.lang.ClassLoader.findLoadedClass()
// [4] jdk.internal.loader.BuiltinClassLoader.loadClassOrNull()
// [5] jdk.internal.loader.BuiltinClassLoader.loadClass()
// [6] jdk.internal.loader.ClassLoaders\(AppClassLoader.loadClass(), or // jdk.internal.loader.ClassLoaders\)PlatformClassLoader.loadClass()

除此之外,还有一些,如JVM_DefineClass1也有机会走到CDS 的逻辑。