专栏名称: ImportNew
伯乐在线旗下账号,专注Java技术分享,包括Java基础技术、进阶技能、架构设计和Java技术领域动态等。
目录
相关文章推荐
芋道源码  ·  这 30 款 IDEA 宝贝插件 yyds ·  17 小时前  
芋道源码  ·  知乎热议:为什么很多程序员讨厌低代码? ·  2 天前  
芋道源码  ·  Redission 分布式锁原理分析 ·  4 天前  
51好读  ›  专栏  ›  ImportNew

JVM 源码分析之 Java 类的加载过程

ImportNew  · 公众号  · Java  · 2017-02-27 20:40

正文

(点击上方公众号,可快速关注)


来源:占小狼,

www.jianshu.com/p/252e27863822

如有好文章投稿,请点击 → 这里了解详情


乘着年轻,多学习


背景


最近对Java细节的底层实现比较感兴趣,如Java类文件是如何加载到虚拟机的,类对象和方法是以什么数据结构存在于虚拟机中?虚方法、实例方法和静态方法是如何调用的?本文基于openjdk-7的OpenJDK实现Java类在HotSpot的内部实现进行分析。


HotSpot内存划分


在HotSpot实现中,内存被划分成Java堆、方法区、Java栈、本地方法栈和PC寄存器几个部分:


  1. Java栈和本地方法栈用于方法之间的调用,进栈出栈的过程;


  2. Java堆用于存放对象,在Java中,所有对象的创建都在堆上申请内存,并被GC管理;


  3. 方法区分成PermGen和CodeCache:PermGen存放Java类的相关信息,如静态变量、成员方法和抽象方法等;CodeCache存放JIT编译之后的本地代码;


更详细的相关内容可以阅读《JVM内存的那些事》(http://www.importnew.com/23629.html)。


HotSpot对象模型


HotSpot JVM并没有根据Java对象直接通过虚拟机映射到新建的C++对象,而是设计了一个oop/klass model,其中oop为Ordinary Object Pointer,用来表示对象的实例信息;klass用来保存描述元数据。


Klass



关于为何要设计oop/klass这种二分模型的实现,一个原因是不想让每个对象都包含vtbl(虚方法表),其中oop中不含有任何虚函数,虚函数表保存于klass中,可以进行method dispatch。


oop



oopDesc对象包含两部分数据:_mark 和 _metadata;


  1. _mark是markOop类型对象,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,占用内存大小与虚拟机位长一致,更具体的实现可以阅读 《 java对象头的HotSpot实现分析 》(http://www.importnew.com/23605.html)。


  2. _metadata是一个结构体,wideKlassOop和narrowOop都指向InstanceKlass对象,其中narrowOop指向的是经过压缩的对象;


  3. _klass字段建立了oop对象与klass对象之间的联系;


HotSpot如何加载并解析class文件


class文件在虚拟机的整个生命周期包括加载、验证、准备、解析、初始化、使用和卸载7个阶段,通过ClassLoader.loadClass方法可以手动加载一个Java类到虚拟机中,并返回Class类型的引用。



这里并没有自定义类加载器,而是利用ClassLoaderCase的类加载器进行加载类AAA。


loadClass方法实现



  1. loadClass方法实现了双亲委派的类加载机制,如果需要自定义类加载器,建议重写内部的findClass方法,而非loadClass方法;


  2. 通过debug,可以发现loadClass方法最终会执行native方法defineClass1进行类的加载,即读取对应class文件的二进制数据到虚拟机中进行解析;


class文件的解析


Java中的defineClass1方法是个native方法,说明依赖于底层的实现,在HotSpot中,其实现位于ClassLoader.c文件中,最终调用jvm.cpp中的jvm_define_class_common方法实现,核心的实现逻辑如下:



1、验证全限定类名的长度,最大为(1


2、SystemDictionary::resolve_from_stream处理stream数据流,并生成Klass对象。内部通过ClassFileParser.cpp的parseClassFile方法对class文件的数据流进行解析,代码实在实在实在实在太长,有兴趣的同学可以阅读完整的实现,大概的过程如下:


  1. 验证当前magic为0xCAFEBABE;

  2. 获取class文件的minor_version、major_version,并判断当前虚拟机是否支持该版本;

  3. 通过parse_constant_pool方法解析当前class的常量池;

  4. 解析当前class的access_flags;

  5. 解析当前class的父类;

  6. 解析当前class的接口;

  7. ….


好吧,我得承认这块逻辑很复杂…


class数据流解析完成后,通过oopFactory::new_instanceKlass创建一个与之对应的instanceKlass对象,new_instanceKlass实现如下:



1、其中instanceKlassKlass::allocate_instance_klass方法会初始化一个空instanceKlass对象,并由后续逻辑进行数据的填充;


2、但是发现该方法的返回类型并非是instanceKlass,而是klassOop类型;


3、allocate_instance_klass方法的实现如下:



1、base_create_klass方法最终通过Klass::base_create_klass_oop方法创建Klass对象,这里是instanceKlass对象,并返回对应的klassOop;


2、k()->klass_part()获取对应的Klass对象,并强制转换成instanceKlass类型的对象;


3、设置instanceKlass对象的默认值;


Klass对象如何创建?


上述的instanceKlass对象由Klass::base_create_klass_oop方法进行创建,实现如下:



1、allocate_permanent方法默认在PermGen分配内存,instanceKlass对象保存在永久代区域;


2、Klass的as_klassOop方法可以获取对应的klassOop,那klassOop到底是什么?



klassOop相当于Java中的class,一个klassOop对象包含header、klass_field和Klass。


instanceKlass



可以发现,每个instanceKlass对象都有一个ClassState状态,用来标识当前class的加载进度,另外instanceKlass对象中包含了如下字段,描述class文件的信息。



instanceKlassKlass


instanceKlassKlass在实现上继承了klassKlass类



全局只存在一个instanceKlassKlass对象,虚拟机启动时,会在Universe::genesis方法中初始化。



虚拟机中所有instanceKlass对象的_klass字段都指向该instanceKlassKlass对象,其初始化过程如下:



1、方法Universe::klassKlassObj()获取klassKlass对象;


2、方法base_create_klass负责创建instanceKlassKlass对象,并返回对应的klassOop;


3、方法java_lang_Class::create_mirror分配mirror,类似于一个镜像,在java层面可以访问到;


klassKlass


klassKlass在实现上继承了Klass类



和instanceKlassKlass一样,klassKlass对象也是全局唯一的,虚拟机启动时,会在Universe::genesis方法中初始化,其初始化过程如下:



1、通过base_create_klass创建klassKlass对象,并返回对应的klassOop;


2、set_klass方法把自身设置成_klass;


看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能