vlambda博客
学习文章列表

jvm如何加载class、双亲委派、内存结构、对象内存布局、jvm常用指令 | 一周实验室

JVM如何加载class

加载阶段(Loading)、

连接阶段(Linking)、

初始化阶段(Initializing)

jvm如何加载class、双亲委派、内存结构、对象内存布局、jvm常用指令 | 一周实验室

加载阶段(Loading)

加载阶段主要做3件事情:

  1. 通过类全限定名获取定义此类的二进制字节流;

  2. 将字节流代表的静态存储结构转换为方法区的运行时数据结构;

  3. 在内存中生成此类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

连接阶段(Linking)

Linking的过程分为三小步

  • Verification是校验装进来的class文件是不是符合class文件的标准,假如你装进来的不是这个CA FE BA BE,在这个步骤就被拒绝掉了。

  • Preparation是把class文件静态变量赋默认值,不是赋初始值,比如你static i = 8 ,注意这个步骤8并不是把i值赋成8,而是先赋0。

初始化阶段(Initializing)

Initlalizing意思是把静态变量赋初始值。

双亲委派机制

jvm如何加载class、双亲委派、内存结构、对象内存布局、jvm常用指令 | 一周实验室

双亲委派是一个孩子向父亲方向,然后父亲向孩子方向的双亲委派过程。

一个class文件需要被load内存的时候是这样的


任何一个class,假如你定义了ClassLoader,这时候就先尝试去自定义的ClassLoader里面找,他内部维护着缓存,先问你有没有帮我把这个class加载进来了?如果加载进来了就不需要加载第二遍了,如果没有加载进来,那就赶紧把我加载进来。


他如果在自定义的ClassLoader缓存中没有找到的话,他并不是直接加载这个类,他会先去他的父亲Application父加载器,说爸爸你有没有把这个类加载进来了呀?这个时候Application也会去他的缓存里面找有没有这个类,如果有则返回,如果没有则委托给他的父亲Bootstrap,同理Bootstrap也会去他的缓存中找看有没有这个类,有则返回,没有则继续委托给他的父亲Extendsion去加载,这时候Extendsion说我只负责加载扩展jar包里的类,你的类我找不到,麻烦Application去加载,Application又说我只是负责加载classpath路径下的类,其他的我也找不到,然后再委托给自定义的ClassLoader去加载。


整个过程经过了一圈转了一圈,才真正把这个类加载进来,当我们能够把这个类加载进来的时候叫做成功,如果加载不进来,抛出异常ClassNotfoundException。


这就叫双亲委派。

CPU的内存结构以及Java Memory Model

jvm如何加载class、双亲委派、内存结构、对象内存布局、jvm常用指令 | 一周实验室

CPU计算核心与内存之间有三级缓存,缓存离CPU计算核心越远容量越大、速度越慢,反之则容量越小、速度越快。


CPU计算核心与内存之间存在着三级缓存,那么这个时候就会带来数据一致性的问题,如果在单线程的环境数据一致性是没有问题的,但是在多线程环境中就很难保证数据的一致性了。为了解决这个问题,CPU引入了一个数据一致性的协议,这个协议叫MESI。


关于MESI协议更详细的描述,大家可以查看这篇文章


jvm如何加载class、双亲委派、内存结构、对象内存布局、jvm常用指令 | 一周实验室

Java的内存模型(Java Memory Model)其实就是仿照CPU的内存结构来设计和定义的。


JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。


Java内存模型中规定了所有的变量都存储在主内存中,每个线程有自己的工作内存(类比缓存理解),线程的工作内存中保存了该线程使用到主内存中的变量拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递(通信)均需要在主内存来完成。


jvm如何加载class、双亲委派、内存结构、对象内存布局、jvm常用指令 | 一周实验室

Java对象的内存布局

在 HotSpot虚拟机中,对象在内存中的存储布局分为三块区域:

对象头(Header)

实例数据(Instance Data)

对齐填充(Padding)



一个java对象在内存中占多少个字节呢?


  • 64位系统(未开启指针压缩):Mark Word占用8个字节 + Class Pointer占用8个字节 = 16个字节 (16已经是8的整数倍,所以不需要对齐填充)

       对象头的大小:16个字节

  • 64位系统(开启指针压缩):Mark Word 占用8个字节 + Class Pointer 占用4个字节 + 对齐填充 4个字节 = 16个字节 (空对象,所以实例数据大小为0)

        对象头的大小:12个字节

JVM常用指令

JVM 基本指令


指令 释义
iconst_1 int型常量值1进栈
bipush 将一个byte型常量值推送至栈顶
iload_1 第二个int型局部变量进栈,从0开始计数
istore_1 将栈顶int型数值存入第二个局部变量,从0开始计数
iadd 栈顶两int型数值相加,并且结果进栈
return 当前方法返回void
getstatic 获取指定类的静态域,并将其值压入栈顶
putstatic 为指定的类的静态域赋值
invokevirtual 调用实例方法
invokespecial 调用超类构造方法、实例初始化方法、私有方法
invokestatic 调用静态方法
invokeinterface 调用接口方法
new 创建一个对象,并且其引用进栈
newarray 创建一个基本类型数组,并且其引用进栈


有话想说

以上就是本周实验室的全部内容,希望每周的分享都能让大家有所收获。


代码实验室的创建已经有一段时间了,建立本号的初衷是为了做技术的分享,通过系统的整理JVM的基础知识让大家更加了解Java底层的运行逻辑,要知其然,更要知其所以然。



最后,感谢大家的阅读~