类文件解析的入口是ClassFileParser类中定义的parseClassFile()方法。上一小节得到了文件字节流stream后,接着会在ClassLoader::load_classfile()函数中调用parseClassFile()函数,调用的源代码实现如下:

源代码位置:src/share/vm/classfile/classLoader.cpp
instanceKlassHandle h;
if (stream != NULL) {
// class file found, parse it
ClassFileParser parser(stream);
ClassLoaderData* loader_data = ClassLoaderData::the_null_class_loader_data();
Handle protection_domain;
TempNewSymbol parsed_name = NULL;
instanceKlassHandle result =
parser.parseClassFile(h_name,loader_data,protection_domain,parsed_name,false,CHECK_(h));
// add to package table
if (add_package(name, classpath_index, THREAD)) {
h = result;
}
}

另外还有一些函数也会在必要的时候调用parseClassFile()函数,如装载Java主类时调用的SystemDictionary::resolve_from_stream()函数等。

调用的parseClassFile()函数的实现如下:  

instanceKlassHandle parseClassFile(Symbol* name,
ClassLoaderData* loader_data,
Handle protection_domain,
TempNewSymbol& parsed_name,
bool verify,
TRAPS) {
KlassHandle no_host_klass;
return parseClassFile(name, loader_data, protection_domain, no_host_klass, NULL, parsed_name, verify, THREAD);
}

调用的另外一个方法的原型如下:  

instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name,
ClassLoaderData* loader_data,
Handle protection_domain,
KlassHandle host_klass,
GrowableArray<Handle>* cp_patches,
TempNewSymbol& parsed_name,
bool verify,
TRAPS)

这个方法的实现太复杂,这里简单分几个步骤详细介绍。  

1.  解析魔数、主版本号与次版本号

ClassFileStream* cfs = stream();
...
u4 magic = cfs->get_u4_fast();
guarantee_property(magic == JAVA_CLASSFILE_MAGIC,"Incompatible magic value %u in class file %s",magic, CHECK_(nullHandle));
// Version numbers
u2 minor_version = cfs->get_u2_fast();
u2 major_version = cfs->get_u2_fast();

_major_version = major_version;
_minor_version = minor_version;

读取魔数主要是为了验证值是否为0xCAFEBABE。读取到Class文件的主、次版本号并保存到ClassFileParser实例的_major_version和_minor_version中。  

2.  解析访问标识

// Access flags
AccessFlags access_flags;
jint flags = cfs->get_u2_fast() & JVM_RECOGNIZED_CLASS_MODIFIERS; if ((flags & JVM_ACC_INTERFACE) && _major_version < JAVA_6_VERSION) {
// Set abstract bit for old class files for backward compatibility
flags |= JVM_ACC_ABSTRACT;
}
access_flags.set_flags(flags);

读取并验证访问标识,这个访问标识在进行字段及方法解析过程中会使用,主要用来判断这些字段或方法是定义在接口中还是类中。JVM_RECOGNIZED_CLASS_MODIFIERS是一个宏,定义如下:

#define JVM_RECOGNIZED_CLASS_MODIFIERS (JVM_ACC_PUBLIC     |    \
JVM_ACC_FINAL | \
JVM_ACC_SUPER | \ // 辅助invokespecial指令
JVM_ACC_INTERFACE | \
JVM_ACC_ABSTRACT | \
JVM_ACC_ANNOTATION | \
JVM_ACC_ENUM | \
JVM_ACC_SYNTHETIC)

最后一个标识符是由前端编译器(如Javac等)添加上去的,表示是合成的类型。

3.  解析当前类索引

类索引(this_class)是一个u2类型的数据,类索引用于确定这个类的全限定名。类索引指向常量池中类型为CONSTANT_Class_info的类描述符,再通过类描述符中的索引值找到常量池中类型为CONSTANT_Utf8_info的字符串。

// This class and superclass
u2 this_class_index = cfs->get_u2_fast(); Symbol* class_name = cp->unresolved_klass_at(this_class_index);
assert(class_name != NULL, "class_name can't be null"); // Update _class_name which could be null previously to be class_name
_class_name = class_name;

将读取到的当前类的名称保存到ClassFileParser实例的_class_name属性中。

调用的cp->unresolved_klass_at()方法的实现如下:

源代码位置:/hotspot/src/share/vm/oops/constantPool.hpp

// 未连接的返回Symbol*
// This method should only be used with a cpool lock or during parsing or gc
Symbol* unresolved_klass_at(int which) { // Temporary until actual use
intptr_t* oaar = obj_at_addr_raw(which);
Symbol* tmp = (Symbol*)OrderAccess::load_ptr_acquire(oaar);
Symbol* s = CPSlot(tmp).get_symbol();
// check that the klass is still unresolved.
assert(tag_at(which).is_unresolved_klass(), "Corrupted constant pool");
return s;
}

举个例子如下:

#3 = Class         #17        // TestClass
...
#17 = Utf8 TestClass 

类索引为0x0003,去常量池里找索引为3的类描述符,类描述符中的索引为17,再去找索引为17的字符串,就是“TestClass”。调用obj_at_addr_raw()方法找到的是一个指针,这个指针指向表示“TestClass”这个字符串的Symbol对象,也就是在解析常量池项时会将本来存储索引值17替换为存储指向Symbol对象的指针。 

调用的obj_at_addr_raw()方法的实现如下:

intptr_t*   obj_at_addr_raw(int which) const {
assert(is_within_bounds(which), "index out of bounds");
return (intptr_t*) &base()[which];
}
intptr_t* base() const {
return (intptr_t*) (
( (char*) this ) + sizeof(ConstantPool)
);
}

base()是ConstantPool中定义的方法,所以this指针指向当前ConstantPool对象在内存中的首地址,加上ConstantPool类本身需要占用的内存大小后,指针指向了常量池相关信息,这部分信息通常就是length个指针宽度的数组,其中length为常量池数量。通过(intptr_t*)&base()[which]获取到常量池索引which对应的值,对于上面的例子来说就是一个指向Symbol对象的指针。 

4.  解析父类索引

父类索引(super_class)是一个u2类型的数据,父类索引用于确定这个类的父类全限定名。由于java语言不允许多重继承,所以父类索引只有一个。父类索指向常量池中类型为CONSTANT_Class_info的类描述符,再通过类描述符中的索引值找到常量池中类型为CONSTANT_Utf8_info的字符串。

u2 super_class_index = cfs->get_u2_fast();
instanceKlassHandle super_klass = parse_super_class(super_class_index,CHECK_NULL);

调用的parse_super()方法的实现如下:

instanceKlassHandle ClassFileParser::parse_super_class(int super_class_index,TRAPS) {

  instanceKlassHandle super_klass;
if (super_class_index == 0) { // 当为java.lang.Object类时,没有父类
check_property(_class_name == vmSymbols::java_lang_Object(),
"Invalid superclass index %u in class file %s",super_class_index,CHECK_NULL);
} else {
check_property(valid_klass_reference_at(super_class_index),
"Invalid superclass index %u in class file %s",super_class_index,CHECK_NULL);
// The class name should be legal because it is checked when parsing constant pool.
// However, make sure it is not an array type.
bool is_array = false;
constantTag mytemp = _cp->tag_at(super_class_index);
if (mytemp.is_klass()) {
super_klass = instanceKlassHandle(THREAD, _cp->resolved_klass_at(super_class_index));
}
}
return super_klass;
}

如果类已经连接,那么可通过super_class_index直接找到表示父类的InstanceKlass实例,否则返回的值就是NULL。 

resolved_klass_at()方法的实现如下:

源代码位置:/hotspot/src/share/vm/oops/constantPool.hpp
// 已连接的返回Klass*
Klass* resolved_klass_at(int which) const { // Used by Compiler
// Must do an acquire here in case another thread resolved the klass
// behind our back, lest we later load stale values thru the oop.
Klass* tmp = (Klass*)OrderAccess::load_ptr_acquire(obj_at_addr_raw(which));
return CPSlot(tmp).get_klass();
} 

其中的CPSlot类的实现如下:

class CPSlot VALUE_OBJ_CLASS_SPEC {
intptr_t _ptr;
public:
CPSlot(intptr_t ptr): _ptr(ptr) {}
CPSlot(Klass* ptr): _ptr((intptr_t)ptr) {}
CPSlot(Symbol* ptr): _ptr((intptr_t)ptr | 1) {} // 或上1表示已经解析过了,Symbol*本来不需要解析 intptr_t value() { return _ptr; }
bool is_resolved() { return (_ptr & 1) == 0; }
bool is_unresolved() { return (_ptr & 1) == 1; } Symbol* get_symbol() {
assert(is_unresolved(), "bad call");
return (Symbol*)(_ptr & ~1);
}
Klass* get_klass() {
assert(is_resolved(), "bad call");
return (Klass*)_ptr;
}
};  

5.  解析实现接口 

接口表,interfaces[]数组中的每个成员的值必须是一个对constant_pool表中项目的一个有效索引值, 它的长度为 interfaces_count。每个成员interfaces[i] 必须为CONSTANT_Class_info类型常量,其中 0 ≤ i <interfaces_count。在interfaces[]数组中,成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)一样,即interfaces[0]对应的是源代码中最左边的接口。

u2 itfs_len = cfs->get_u2_fast();
Array<Klass*>* local_interfaces =
parse_interfaces(itfs_len, protection_domain, _class_name,&has_default_methods, CHECK_(nullHandle));

parse_interfaces()方法的实现如下:

Array<Klass*>* ClassFileParser::parse_interfaces(int     length,
Handle protection_domain,
Symbol* class_name,
bool* has_default_methods,
TRAPS
){
if (length == 0) {
_local_interfaces = Universe::the_empty_klass_array();
} else {
ClassFileStream* cfs = stream();
_local_interfaces = MetadataFactory::new_array<Klass*>(_loader_data, length, NULL, CHECK_NULL); int index;
for (index = 0; index < length; index++) {
u2 interface_index = cfs->get_u2(CHECK_NULL);
KlassHandle interf; if (_cp->tag_at(interface_index).is_klass()) {
interf = KlassHandle(THREAD, _cp->resolved_klass_at(interface_index));
} else {
Symbol* unresolved_klass = _cp->klass_name_at(interface_index); Handle class_loader(THREAD, _loader_data->class_loader()); // Call resolve_super so classcircularity is checked
Klass* k = SystemDictionary::resolve_super_or_fail(class_name,
unresolved_klass,
class_loader,
protection_domain,
false, CHECK_NULL);
// 将表示接口的InstanceKlass实例封装为KlassHandle实例
interf = KlassHandle(THREAD, k);
} if (InstanceKlass::cast(interf())->has_default_methods()) {
*has_default_methods = true;
}
_local_interfaces->at_put(index, interf());
} if (!_need_verify || length <= 1) {
return _local_interfaces;
}
}
return _local_interfaces;
}

循环对类实现的每个接口进行处理,通过interface_index找到接口在C++类中的表示InstanceKlass实例,然后封装为KlassHandle后,存储到_local_interfaces数组中。需要注意的是,如何通过interface_index找到对应的InstanceKlass实例,如果接口索引在常量池中已经是对应的InstanceKlass实例,说明已经连接过了,直接通过_cp_resolved_klass_at()方法获取即可;如果只是一个字符串表示,需要调用SystemDictionary::resolve_super_or_fail()方法进行连接,这个方法在连接时会详细介绍,这里不做过多介绍。

klass_name_at()方法的实现如下:

Symbol* ConstantPool::klass_name_at(int which) {
assert(tag_at(which).is_unresolved_klass() || tag_at(which).is_klass(),
"Corrupted constant pool");
// A resolved constantPool entry will contain a Klass*, otherwise a Symbol*.
// It is not safe to rely on the tag bit's here, since we don't have a lock, and the entry and
// tag is not updated atomicly.
CPSlot entry = slot_at(which);
if (entry.is_resolved()) { // 已经连接时,获取到的是指向InstanceKlass实例的指针
// Already resolved - return entry's name.
assert(entry.get_klass()->is_klass(), "must be");
return entry.get_klass()->name();
} else { // 未连接时,获取到的是指向Symbol实例的指针
assert(entry.is_unresolved(), "must be either symbol or klass");
return entry.get_symbol();
}
}

其中的slot_at()方法的实现如下:  

CPSlot slot_at(int which) {
assert(is_within_bounds(which), "index out of bounds");
// Uses volatile because the klass slot changes without a lock.
volatile intptr_t adr = (intptr_t)OrderAccess::load_ptr_acquire(obj_at_addr_raw(which));
assert(adr != 0 || which == 0, "cp entry for klass should not be zero");
return CPSlot(adr);
}

同样调用obj_at_addr_raw()方法,获取ConstantPool中对应索引处存储的值,然后封装为CPSlot对象返回即可。

6.  解析类属性

ClassAnnotationCollector parsed_annotations;
parse_classfile_attributes(&parsed_annotations, CHECK_(nullHandle));

调用parse_classfile_attributes()方法解析类属性,方法的实现比较繁琐,只需要按照各属性的格式来解析即可,有兴趣的读者可自行研究。

关于常量池、字段及方法的解析在后面将详细介绍,这里暂时不介绍。

相关文章的链接如下:

1、在Ubuntu 16.04上编译OpenJDK8的源代码

2、调试HotSpot源代码

3、HotSpot项目结构 

4、HotSpot的启动过程

5、HotSpot二分模型(1)

6、HotSpot的类模型(2)

7、HotSpot的类模型(3)

8、HotSpot的类模型(4)

9、HotSpot的对象模型(5)

10、HotSpot的对象模型(6)

11、操作句柄Handle(7)

12、句柄Handle的释放(8)

13、类加载器

14、类的双亲委派机制

15、核心类的预装载

16、Java主类的装载

17、触发类的装载

18、类文件介绍

19、文件流

作者持续维护的个人博客classloading.com

关注公众号,有HotSpot源码剖析系列文章!

   

  

解析Class文件的更多相关文章

  1. Android 解析XML文件和生成XML文件

    解析XML文件 public static void initXML(Context context) { //can't create in /data/media/0 because permis ...

  2. CSharpGL(9)解析OBJ文件并用CSharpGL渲染

    CSharpGL(9)解析OBJ文件并用CSharpGL渲染 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码中包含10多个独立的Demo ...

  3. Jsoup系列学习(2)-解析html文件

    解析html文件 1.当我们通过发送http请求时,有时候返回结果是一个html格式字符串,你需要从一个网站获取和解析一个HTML文档,并查找其中的相关数据.你可以使用下面解决方法: 使用 Jsoup ...

  4. JAVA使用SAX解析XML文件

    在我的另一篇文章(http://www.cnblogs.com/anivia/p/5849712.html)中,通过一个例子介绍了使用DOM来解析XML文件,那么本篇文章通过相同的XML文件介绍如何使 ...

  5. JAVA中使用DOM解析XML文件

    XML是一种方便快捷高效的数据保存传输的格式,在JSON广泛使用之前,XML是服务器和客户端之间数据传输的主要方式.因此,需要使用各种方式,解析服务器传送过来的信息,以供使用者查看. JAVA作为一种 ...

  6. CSharpGL(5)解析3DS文件并用CSharpGL渲染

    CSharpGL(5)解析3DS文件并用CSharpGL渲染 我曾经写过一个简单的*.3ds文件的解析器,但是只能解析最基本的顶点.索引信息,且此解析器是仿照别人的C++代码改写的,设计的也不好,不方 ...

  7. php解析.csv文件

    public function actionImport() { //post请求过来的 $fileName = $_FILES['file']['name']; $fileTmpName = $_F ...

  8. java中采用dom4j解析xml文件

    一.前言 在最近的开发中用到了dom4j来解析xml文件,以前听说过来解析xml文件的几种标准方式:但是从来的没有应用过来,所以可以在google中搜索dmo4j解析xml文件的方式,学习一下dom4 ...

  9. 使用XStream解析MXL文件用到的jar包---xpp3_min-1.1.3.4.O.jar和xstream-1.3.1.jar

    使用XStream解析MXL文件用到的jar包---xpp3_min-1.1.3.4.O.jar和xstream-1.3.1.jar

  10. XML:使用DOM技术解析xML文件中的城市,实现select级联选择

    中国的城市xml格式:cities.xml <?xml version="1.0" encoding="utf-8"?> <china> ...

随机推荐

  1. 云小课 | “VPC连接”知多少

    摘要:华为云提供了丰富的网络服务,可满足多种网络互连场景. 同Region的两个VPC怎么连通?” “跨Region的两个VPC又怎么连通?” “VPC内的ECS搭建了一个应用,需要访问Interne ...

  2. 北航2018级算法期末上机实录随笔1st

    简单记录下题目类型和做题情况,理性复习同时也希望提供一些参考 题目描述 共计八个题目,按照助教的划分,题目分类如下 一个签到(二分查找),两个板子(活动选择.KMP(洛谷kmp模板题)),一个板子变形 ...

  3. 某cms代码审计

    前言   前几个礼拜上课老师带着挖cms的洞,挖出个cookie反序列化注入漏洞,和报错注入并且提交了cnvd.昨天去找源码的时候发现它更新了一个版本,更新日志上也没说修复,就想着看看漏洞还存不存在, ...

  4. 阿里云OSS 服务端签名后直传之分片上传(结合element-ui的upload组件)

    分片上传(结合element-ui的upload组件实现自定义上传) async uploadFree(content){ let data = await this.getOssToken(); / ...

  5. scrapy (三) : 请求传参

    scrapy 请求传参 1.定义数据结构item.py文件 ''' field: item.py ''' # -*- coding: utf-8 -*- # Define here the model ...

  6. python 装饰器(一):装饰器基础(一)装饰器形式,何时执行

    简介 装饰器是可调用的对象,其参数是另一个函数(被装饰的函数). 装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象. 形式 假如有个名为 decorate 的装饰器: ...

  7. 大话深度学习:B站Up主麦叔教你零代码实现图像分类神经网络

    之前,我在B站发布了“大话神经网络,10行代码不调包,听不懂你打我!”的视频后,因为简单易懂受到了很多小伙伴的喜欢! 但也有小伙伴直呼不够过瘾,因为大话神经网络只有4个神经元. 也有小伙伴问不写代码, ...

  8. 一位Google高管审查了20,000+简历,他发现了这5个致命的错误

    工作与生活的平衡 下班划水摸鱼时间,我比较喜欢浏览一下各类新闻网页,比如说ins,这不,我就在ins上看到了这样的一篇文章,内容很简单,就是简历,但是就是这样一份简历,却让这位Google高管震惊不已 ...

  9. 网课神器之obs-studio的安装使用

    obs-studio 首先,下载obs-studio安装文件,然后点击安装. 建议安装完后直接跳过配置,然后进入文件-设置-通用-系统托盘-勾选"总是最小化到系统托盘,而不是任务栏" ...

  10. 使用SQL语句进行特定值排序

    使用SQL语句进行查询时,对数据进行排序,排序要求为排序的一个字段中特定值为顶部呈现: select * from TableName order by case TableFieldName whe ...