类文件解析的入口是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. SCOI 2016 萌萌哒

    SCOI 2016 萌萌哒 solution 有点线段树的味道,但是并不是用线段树来做,而是用到另外一个区间修改和查询的利器--ST表 我们可以将一个点拆成\(logN\)个点,分别代表从点\(i\) ...

  2. 线性dp—奶牛渡河

    题目 Farmer John以及他的N(1 <= N <= 2,500)头奶牛打算过一条河,但他们所有的渡河工具,仅仅是一个木筏. 由于奶牛不会划船,在整个渡河过程中,FJ必须始终在木筏上 ...

  3. The Meaningless Game,算是思维吧。

    题目直接链接 题意: 某游戏规则:每次选定数字k(正整数),两人初始分数为1,获胜者分数乘k2,失败者分数成k,给你两个数字,判断是否可能是本游戏的两人的得分. 分析: 为啥题意我不写判断两个数可不可 ...

  4. CVE-2020-5902 F5 BIG-IP 远程代码执行RCE

    cve-2020-5902 : RCE:curl -v -k 'https://[F5 Host]/tmui/login.jsp/..;/tmui/locallb/workspace/tmshCmd. ...

  5. dva的简介

    dva的定义 dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装; redux 他是react当中的仓库,如果熟悉vue的话,他的功能 ...

  6. sqlilabs 1-20关 payload

    1.联合查询注入:http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,user(),3 --+http://127.0.0.1/sqli/Less- ...

  7. 使用命名管道承载gRPC

    最近GRPC很火,感觉整RPC不用GRPC都快跟不上时髦了. gRPC设计 刚好需要使用一个的RPC应用系统,自然而然就盯上了它,但是它真能够解决所有问题吗?不见得,先看看他的优点: gRPC是一种与 ...

  8. WPF基于.Net Core

    WPF基于.Net Core 因为最近.net core的热门,所以想实现一下.net core框架下的WPF项目,还是MVVM模式,下面就开始吧,简单做一个计算器吧. 使用VS2019作为开发工具 ...

  9. The solution for apt-get update Err 404

    最近在ubuntu 12.10上执行sudo apt-get update 命令后出现了如下错误: Ign http://extras.ubuntu.com natty/main Translatio ...

  10. How to install chinese input method

    在Ubuntu中安装中文输入法确实比较麻烦,特别是英文版的Ubuntu系统   Ubuntu上的输入法主要有小小输入平台(支持拼音/二笔/五笔等),Fcitx,Ibus,Scim等.其中Scim和Ib ...