jvm如何加载class双亲委派内存结构对象内存布局jvm常用指令 | 一周实验室
Posted Seven的代码实验室
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jvm如何加载class双亲委派内存结构对象内存布局jvm常用指令 | 一周实验室相关的知识,希望对你有一定的参考价值。
JVM如何加载class
加载阶段(Loading)、
连接阶段(Linking)、
初始化阶段(Initializing)
加载阶段(Loading)
加载阶段主要做3件事情:
通过类全限定名获取定义此类的二进制字节流;
将字节流代表的静态存储结构转换为方法区的运行时数据结构;
在内存中生成此类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
连接阶段(Linking)
Linking的过程分为三小步
Verification是校验装进来的class文件是不是符合class文件的标准,假如你装进来的不是这个CA FE BA BE,在这个步骤就被拒绝掉了。
Preparation是把class文件静态变量赋默认值,不是赋初始值,比如你static i = 8 ,注意这个步骤8并不是把i值赋成8,而是先赋0。
初始化阶段(Initializing)
Initlalizing意思是把静态变量赋初始值。
双亲委派机制
双亲委派是一个孩子向父亲方向,然后父亲向孩子方向的双亲委派过程。
一个class文件需要被load内存的时候是这样的
任何一个class,假如你定义了ClassLoader,这时候就先尝试去自定义的ClassLoader里面找,他内部维护着缓存,先问你有没有帮我把这个class加载进来了?如果加载进来了就不需要加载第二遍了,如果没有加载进来,那就赶紧把我加载进来。
他如果在自定义的ClassLoader缓存中没有找到的话,他并不是直接加载这个类,他会先去他的父亲Application父加载器,说爸爸你有没有把这个类加载进来了呀?这个时候Application也会去他的缓存里面找有没有这个类,如果有则返回,如果没有则委托给他的父亲Bootstrap,同理Bootstrap也会去他的缓存中找看有没有这个类,有则返回,没有则继续委托给他的父亲Extendsion去加载,这时候Extendsion说我只负责加载扩展jar包里的类,你的类我找不到,麻烦Application去加载,Application又说我只是负责加载classpath路径下的类,其他的我也找不到,然后再委托给自定义的ClassLoader去加载。
整个过程经过了一圈转了一圈,才真正把这个类加载进来,当我们能够把这个类加载进来的时候叫做成功,如果加载不进来,抛出异常ClassNotfoundException。
这就叫双亲委派。
CPU的内存结构以及Java Memory Model
CPU计算核心与内存之间有三级缓存,缓存离CPU计算核心越远容量越大、速度越慢,反之则容量越小、速度越快。
CPU计算核心与内存之间存在着三级缓存,那么这个时候就会带来数据一致性的问题,如果在单线程的环境数据一致性是没有问题的,但是在多线程环境中就很难保证数据的一致性了。为了解决这个问题,CPU引入了一个数据一致性的协议,这个协议叫MESI。
关于MESI协议更详细的描述,大家可以查看这篇文章
Java的内存模型(Java Memory Model)其实就是仿照CPU的内存结构来设计和定义的。
JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。
Java内存模型中规定了所有的变量都存储在主内存中,每个线程有自己的工作内存(类比缓存理解),线程的工作内存中保存了该线程使用到主内存中的变量拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递(通信)均需要在主内存来完成。
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底层的运行逻辑,要知其然,更要知其所以然。
最后,感谢大家的阅读~
以上是关于jvm如何加载class双亲委派内存结构对象内存布局jvm常用指令 | 一周实验室的主要内容,如果未能解决你的问题,请参考以下文章