此处我们开始踏上类文件解析的道路.在此之前,我们已经讲解了类加载的流程,而在类加载的流程的过程中,涉及到类文件解析的过程,由于过程很复杂,因此用一个系列来进行介绍.(其实,在之前介绍的类加载的流程可以准确的).在此处贴下之前的博客目录:
解析
此处先介绍一下ClassFileParser::parseClassFile的核心步骤:
-
读取魔数并进行校验其是否为0xCAFEBABE
-
读取class文件主,次版本号及验证,检查版本号支持是否支持,若不支持,则抛出java.lang.UnsupportedClassVersionError
-
解析常量池
-
读取访问标识
- 处理JVM_ACC_INTERFACE的后向兼容问题,如果访问标记为JVM_ACC_INTERFACE,并且主版本号小于50,则同时设置JVM_ACC_ABSTRACT,保证后向兼容
- 验证类的修饰符并设置
-
读取当前类索引,并按索引在常量池项中找到当前类的全限定名
-
读取父类索引,并按索引在常量池中找到父类的全限定名和父类句柄
- 如果是为0的话,则检查其类名是否是java.lang.Object
- 检查其对应的类型为klass,检查其类型不是数组,如果是数组,则抛出异常
-
读取接口信息,接口类型包括本地接口和父类传递接口
- 如果接口不存在,则返回一个空数组
- 解析接口
-
解析类变量,并计算出域大小和偏移量(oop-map)信息,并根据域分配策略对字段存储顺序进行分配.这些信息都将在后续步骤填入instanceKlass对象中成为类信息的一部分
-
读取方法信息.根据从class中解析出的method信息创建了methodOop对象
-
解析父类
-
根据已解析的父类,方法,接口等信息得到vtable,itable 大小
-
计算字段的偏移量
-
创建当前类instanceKlass并按照上述步骤已解析好的信息为该对象赋值,在这期间,同时创建了JAVA镜像类(mirror)
-
解析类文件中的属性
-
通知类已加载,跟新perfdata计数器
ClassFileStream
在进行class 文件解析时,是使用ClassFileStream来进行读取的,其类图如下:
ClassFileStream 其内部维护了一个buffer,该buffer指向class 文件所对应的byte流.
ClassFileStream 不具有对buffer释放的操作,调用者有义务进行buffer的释放,同时在构建流时适当地进行资源标记
该类的字段所代表的意义如下:
- _buffer_start --> 该buffer的起始下标地址
- _buffer_end --> 该buffer的结束下标地址
- _current --> 当前所指向的位置
- _source --> 该ClassFileStream所指向的buffer所对应的源路径(目录名,ZIP/JAR名)
- _need_verify --> 如果true,则会在解析class file时进行验证
创建过程
ClassFileStream 是在ClassLoader::load_classfile方法中,通过遍历class_path 找到要加载的类时进行创建的.其代码如下:
ClassFileStream* stream = NULL;
ClassPathEntry* e = _first_entry;
.....
stream = e->open_stream(name);
此处,在介绍一下ClassPathEntry. 其类图如下:
可见ClassPathEntry 是一个链表结构(因为class path有多个),同时在ClassPathEntry中还声明了一个虚方法,virtual ClassFileStream* open_stream(const char* name) = 0.
此处我们就来看一下ClassPathDirEntry对于该方法的实现.代码如下:
ClassFileStream* ClassPathDirEntry::open_stream(const char* name) {
// construct full path name 1.构造完成路径
char path[JVM_MAXPATHLEN];
if (jio_snprintf(path, sizeof(path), "%s%s%s", _dir, os::file_separator(), name) == -1) {
return NULL;
}
// check if file exists
struct stat st;
if (os::stat(path, &st) == 0) { // 2. 检查该路径是否存在,如果存在的话,则进行读取
// found file, open it
int file_handle = os::open(path, 0, 0);
if (file_handle != -1) {
// read contents into resource array 3.读取内容
u1* buffer = NEW_RESOURCE_ARRAY(u1, st.st_size);
size_t num_read = os::read(file_handle, (char*) buffer, st.st_size);
// close file 4. 关闭文件
os::close(file_handle);
// construct ClassFileStream
if (num_read == (size_t)st.st_size) {
if (UsePerfData) {
ClassLoader::perf_sys_classfile_bytes_read()->inc(num_read);
}
// 5. 构造ClassFileStream
return new ClassFileStream(buffer, st.st_size, _dir); // Resource allocated
}
}
}
return NULL;
}
其中,第5步会执行ClassFileStream的构造函数,其代码如下:
ClassFileStream::ClassFileStream(u1* buffer, int length, char* source) {
_buffer_start = buffer;
_buffer_end = buffer + length;
_current = buffer;
_source = source;
_need_verify = false;
}
ClassFileParser
ClassFileParser 其内部定义了一系列的方法进行class文件,在读取class文件内容是通过ClassFileStream来获取的. 其方法会在后续的过程中进行讲解的.
其构造函数如下:
ClassFileStream* _stream; // Actual input stream
ClassFileParser(ClassFileStream* st) { set_stream(st); }
void set_stream(ClassFileStream* st) { _stream = st; }
解析类文件
解析类文件是在ClassLoader::load_classfile中调用的,代码如下:
ClassFileParser parser(stream);
Handle class_loader;
Handle protection_domain;
symbolHandle parsed_name;
instanceKlassHandle result = parser.parseClassFile(h_name,
class_loader,
protection_domain,
parsed_name,
false,
CHECK_(h));
其调用的方法如下:
instanceKlassHandle parseClassFile(symbolHandle name,
Handle class_loader,
Handle protection_domain,
symbolHandle& parsed_name,
bool verify,
TRAPS) {
KlassHandle no_host_klass;
return parseClassFile(name, class_loader, protection_domain, no_host_klass, NULL, parsed_name, verify, THREAD);
}
最终调用的方法原型如下:
instanceKlassHandle parseClassFile(symbolHandle name,
Handle class_loader,
Handle protection_domain,
KlassHandle host_klass,
GrowableArray<Handle>* cp_patches,
symbolHandle& parsed_name,
bool verify,
TRAPS);
这里涉及的参数值总结如下:
- name --> 要加载class文件的名称,含.class
- class_loader --> 因为此时是在jvm初始化的过程中,因此该Handle 内部所指向的oop 为null.
- protection_domain, parsed_name, no_host_klass --> 这三者所对应的Handle 内部所指向的oop 为null.
- verify --> false
- cp_patches --> NULL
剩余过程,后续介绍…