类文件解析的入口是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. 利用Cython对python代码进行加密

    利用Cython对python代码进行加密 Cython是属于PYTHON的超集,他首先会将PYTHON代码转化成C语言代码,然后通过c编译器生成可执行文件.优势:资源丰富,适合快速开发.翻译成C后速 ...

  2. SpringCloud系列之集成分布式事务Seata应用篇

    目录 前言 项目版本 项目说明 Seata服务端部署 Seata客户端集成 cloud-web module-order module-cart module-goods module-wallet ...

  3. 师兄大厂面试遇到这条 SQL 数据分析题,差点含泪而归!

    写在前面:我是「云祁」,一枚热爱技术.会写诗的大数据开发猿.昵称来源于王安石诗中一句 [ 云之祁祁,或雨于渊 ] ,甚是喜欢. 写博客一方面是对自己学习的一点点总结及记录,另一方面则是希望能够帮助更多 ...

  4. python 迭代器(一):迭代器基础(一) 语言内部使用 iter(...) 内置函数处理可迭代对象的方式

    简介 在 Python 中,所有集合都可以迭代.在 Python 语言内部,迭代器用于支持: 1.for 循环2.构建和扩展集合类型3.逐行遍历文本文件4.列表推导.字典推导和集合推导5.元组拆包6. ...

  5. css换行后缩进,css缩进技巧

    一般情况下像下图这样需要缩进的,一般都会外面一个div里面两块需要两个div然后用定位或者flex.table.浮动后设置宽度等来实现 已知宽度的情况下上面列的方法都适用 不知宽度的情况下可以用fle ...

  6. node -v node is not define

    安装node js  踩过的坑 应该是在CMD 命令里执行 node -v 我却傻傻的跑到 node.js 里执行 node -v 结果就报 node is not define 真相如下图!!!

  7. 设计模式:template method模式

    思想:在父类中定义处理流程的框架,在子类中实现具体的处理方法 优点:在父类中定义处理的算法,无需在每个子类中重复编写 继承关系图: 例子: //接口定义 class Parent { public: ...

  8. 【几何+模拟】二次元变换 计蒜客 - T3213

    题目 aslky 有一个 n×n 的矩形,每个位置上都有一个数,有 q 次操作,每次他会让你上下翻转 (UD),左右反转 (LR),顺时针旋转 90∘(SZ),逆时针旋转 90∘(NZ),请你输出最后 ...

  9. selenium自动爬取网易易盾的验证码

    我们在爬虫过程中难免会遇到一些拦路虎,比如各种各样的验证码,时不时蹦出来,这时候我们需要去识别它来继续我们的工作,接下来我将爬取网一些滑动验证码,然后通过百度的EasyDL平台进行数据标注,创建模型, ...

  10. JSON.stringify和JSON.parse的用法

    用法概述 所有的现代浏览器都支持 JSON 对象,有两个非常有用的方法来处理 JSON 格式的内容: JSON.parse(string) 接受一个 JSON 字符串并将其转换成一个 JavaScri ...