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 的逻辑。