第十九章-ClassLoaderData初始化
讲解本章先从一张图开始
众所周知,Java类的相关信息都是存储在元空间中的,但是是怎么存储的,相信很多读者是不清楚的,这里就不得不涉及到ClassLoaderDataGraph、classLoader、classLoaderData(简称CLD)和Klass的概念及他们四者的关系,这里简单描述下他们的概念,具体细节放到类加载器那一张来讲解。
InstanceKlass(继承自Klass):每个被加载的类在虚拟机中的表示为一个InstanceKlass
ClassLoaderData:类加载器加载类后,存储数据的对象,也就是说被加载的类最终都存储在ClassLoaderData指向的地方,一个CLD可以存入很多被加载的类InstanceKlass,多个InstanceKlass之间通过链表形式存储,且链表头永远是最新加载的类
ClassLoader:类加载器,每个ClassLoader都有一个CLD
ClassLoaderDataGraph:这是CLD的总入口,把所有CLD通过链表管理起来
19.1 根加载器CLD的创建
19.1.1 universe.cpp
19.1.1.1 ClassLoaderData::init_null_class_loader_data
static void init_null_class_loader_data() {
// 验证重复初始化
assert(_the_null_class_loader_data == NULL, "cannot initialize twice");
assert(ClassLoaderDataGraph::_head == NULL, "cannot initialize twice");
// 创建ClassLoaderData对象,第一个加载器的参数是NULL,因为在Java中,没有对根加载器的实现,这个是由虚拟机自身来实现加载的,所以相对Java,这是一个NULL,实现看`章节19.1.2`
_the_null_class_loader_data = new ClassLoaderData((oop)NULL, false, Dependencies());
// 创建完后,赋值给ClassLoaderDataGraph::_head,表示第一个CLD
ClassLoaderDataGraph::_head = _the_null_class_loader_data;
assert(_the_null_class_loader_data->is_the_null_class_loader_data(), "Must be");
if (DumpSharedSpaces) { // 不涉及多Java进程共享,这一步不会走
_the_null_class_loader_data->initialize_shared_metaspaces();
}
}
19.1.2 classLoaderData.cpp
19.1.2.1 ClassLoaderData构造函数
发现这个构造函数,啥也没做,就是对字段赋初始值
ClassLoaderData::ClassLoaderData(Handle h_class_loader, bool is_anonymous, Dependencies dependencies) :
_class_loader(h_class_loader()),
_is_anonymous(is_anonymous),
// An anonymous class loader data doesn't have anything to keep
// it from being unloaded during parsing of the anonymous class.
// The null-class-loader should always be kept alive.
_keep_alive(is_anonymous || h_class_loader.is_null()),
_metaspace(NULL), _unloading(false), _klasses(NULL),
_claimed(0), _jmethod_ids(NULL), _handles(), _deallocate_list(NULL),
_next(NULL), _dependencies(dependencies),
_metaspace_lock(new Mutex(Monitor::leaf+1, "Metaspace allocation lock", true)) {
JFR_ONLY(INIT_ID(this);)
}
19.2 符号表和字符串表
先了解下什么是符号表,什么是字符串表?
SymbolTable(符号表):符号是指字节码中产生的各种元数据的utf-8字符表示,所以符号表就是用来存放这些utf-8字符的
StringTable(字符串表):就是存放Java中String对象的,把StringTable表中的数据都可以想像成与Java中String对应
先这么理解吧,在类加载器那一章节会更细致的讲这两个概念。
以下是函数的部分内容,该函数是universe.cpp->universe_init()
// 创建符号表,把它想像成java里的HashMap
SymbolTable::create_table();
// 创建字符串表,把它想像成java里的HashMap
StringTable::create_table();
// 创建包信息表,也可以想像成一个HashMap,包信息用到的时候再讲
ClassLoader::create_package_info_table();
SymbolTable类的定义:
// 模板类,key为Symbol,value为mtSymbol
class SymbolTable : public RehashableHashtable<Symbol*, mtSymbol>
StringTable类的定义:
// 模板类,key为oop,value为mtSymbol
class StringTable : public RehashableHashtable<oop, mtSymbol>
SymbolTable:key是Symbol,Symbol可以理解为utf8编码的字符信息
SymbolTable:value是mtSymbol,这是一个枚举值,仅仅表示内存的类型解释,不起实际作用
StringTable:key是oop,oop可以理解为指向Java对象的地址(实际上存放的就是Java的String对象的地址)
StringTable:value是mtSymbol,这是一个枚举值,仅仅表示内存的类型解释,不起实际作用
两个表的创建过程都非常简单,但是符号表SymbolTable的创建要复杂些,增加了initialize_symbols初始化符号的操作,代码如下:
static void create_table() {
assert(_the_table == NULL, "One symbol table allowed.");
_the_table = new SymbolTable();
// 预先创建并分配存放符号的内存chunk,symbol_alloc_arena_size = 360K
initialize_symbols(symbol_alloc_arena_size);
}
void SymbolTable::initialize_symbols(int arena_alloc_size) {
// Initialize the arena for global symbols, size passed in depends on CDS.
if (arena_alloc_size == 0) {
_arena = new (mtSymbol) Arena(mtSymbol);
} else {
// 创建一个Arena对象,细节看`章节19.2.1`
_arena = new (mtSymbol) Arena(mtSymbol, arena_alloc_size);
}
}
19.2.1 allocation.cpp
19.2.1.1 Arena构造函数
在JVM运行过程中,会产生大量的符号,为了效率在存储时不可能来一个分配一个,所以需要提前划出一片区域chunk来存储,如果一块chunk不够了,再创建一块,依此类推,Arena就是管理这些chunk的类对象,各chunk之间以链表的形式关联,整个JVM中对内存的管理中,大量使用链表这一数据结构来处理。顺便描述下Arena的几个字段的含义
Chunk *_first; // 第一块 chunk
Chunk *_chunk; // 前在用的 chunk
char *_hwm, *_max; // 当前在用的 chunk 起始点和限制点
size_t _size_in_bytes; // Arena的总大小(所有chunk大小相加)
Arena::Arena(MEMFLAGS flag, size_t init_size) : _flags(flag), _size_in_bytes(0) {
size_t round_size = (sizeof (char *)) - 1;
init_size = (init_size+round_size) & ~round_size;
// 主要看这里,创建了一个init_size大小的chunk块
_first = _chunk = new (AllocFailStrategy::EXIT_OOM, init_size) Chunk(init_size);
_hwm = _chunk->bottom(); // 保存 hwm, max,分别指向chunk块可操作的起始点和限制点
_max = _chunk->top();
MemTracker::record_new_arena(flag);
set_size_in_bytes(init_size);
}