java虚拟机知识点简要梳理

时间:2022-12-26 21:44:42
首先来看一个java虚拟机的思维导图,下面每个知识点都可以进行展开,本篇只做简要梳理

上图是从类的整个生命来梳理的,包括类的加载、验证、准备、解析、初始化、使用、卸载,将一一做简要介绍

java虚拟机知识点简要梳理

一、加载
1.加载过程
a.通过类的全限定名获取类的二进制字节流,其中二进制字节流不一定是java语言编译的,只要是最终形成符合java字节流格式即可,比如jruby、jython、groovy等都可以编译成java的二进制字节流
b.将字节流转化为方法区运行时数据结构
c.在内存中生成一个代表该类的java.lang.Class对象,作为该类的访问入口
2.class文件结构
class文件格式采取类似于C语言结构体的伪结构来存储,只有两种数据类型,一个无符号类型,另一个是表,表由多个无符号类型和其他表结构组成的复合数据类型,整个class其实就是一张表。
ClassFile {
u4 magic;//魔数(0xCAFEBABE)
u2 minor_version;//次版本号
u2 major_version;//主版本号
u2 constant_pool_count;//常量池容量计数值
cp_info constant_pool[constant_pool_count-1];//常量池
u2 access_flags;//访问标志
u2 this_class;//类索引
u2 super_class;//父类索引
u2 interfaces_count;//接口计数器
u2 interfaces[interfaces_count];//接口索引集合
u2 fields_count;//字段计数器
field_info fields[fields_count];//字段表
u2 methods_count;//方法计数器
method_info methods[methods_count];//方法表
u2 attributes_count;//属性表计数器
attribute_info attributes[attributes_count];//属性表集合
}
3.双亲委派模型
类加载采用的是双亲委派模型,用下图来说明
java虚拟机知识点简要梳理

类加载器有四种,启动类加载器,是C/C++实现,无法在java代码中调用,扩展类加载器、应用类加载器和自定义类加载器,双亲委派模型指的是当前类加载器加载某个类的时候,如果没有找到,首先调用的父类加载器,如果父类加载器没有找到这个类,则再往上的父类查找,一直到顶层的启动类加载器,如果还是找不到,则启动类加载器尝试加载这个类,如果没有加载成功,则返回空给子加载器,子加载器如果也没有加载到,则继续往下的类加载器走,一直到当前类加载器。过程如下:
(1).如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器去完成。
(2).每一层的类加载器都把类加载请求委派给父类加载器,直到所有的类加载请求都应该传递给顶层的启动类加载器。
(3).如果顶层的启动类加载器无法完成加载请求,子类加载器尝试去加载,如果连最初发起类加载请求的类加载器也无法完成加载请求时,将会抛出ClassNotFoundException,而不再调用其子类加载器去进行类加载。
双亲委派模式的类加载机制的优点是java类它的类加载器一起具备了一种带优先级的层次关系,越是基础的类,越是被上层的类加载器进行加载,保证了java程序的稳定运行。
二、验证
1.文件格式验证
魔法数字、版本号、不支持的常量类型,索引值指向错误等
2.元数据验证
字节码描述信息语义分析。是否有父类、是否继承了不该继承的类、未实现抽象类或接口的全部方法等
3.字节码验证
程序语义是否合法、符合逻辑。
4.符号引用验证
该过程是在解析阶段发生的,符号引用转换成直接引用的时候。能否找到对应的类、符号引用中的类的方法、字段、类是否可被当前类访问等
三、准备
类变量初始化,分配内存并设置初始值("0"值)
四、解析
符号引用转化为直接引用
五、初始化
按照代码中定义的操作进行赋值操作和静态语句块的执行
六、使用
这一块的东西应该是最多的
1.运行时数据区
线程私有:
a.程序计数器
下一条需要执行的字节码命令
b.java虚拟机栈
(1)为每个方法的创建一个栈帧,方法的开始和结束对应栈帧的进栈和出栈
(2)栈帧包含局部变量表,操作数,动态链接,返回地址等
c.本地方法栈
和java虚拟机栈相似,只是执行的是本地方法
线程共享:
d.堆
存放对象实例和数组
e.方法区
保存了类信息,常量,静态变量,即时编译后的代码等数据
2.垃圾收集机制
上面的线程私有的不需要考虑回收问题,因为随着线程的结束或方法的结束,自然会被回收掉,而方法区的中数据保留的是类信息、常量、静态变量等,这一部分的数据很少需要被回收,因此重点在于堆里面的数据,而且这一块的数据是最多的。
(1)垃圾回收的原理
从一系列GC Roots对象出发,向下搜索,所走过的路径被称为引用链,当一个对象到GC Roots没有任何一条引用链时,该对象是不可用的,应该被回收掉。(可达性分析)
能够作为GC Roots基本上收集器很少或不会作用到的地方,比如方法区的静态变量或常量引用的对象,虚拟机栈或本地方法栈的引用的对象。
(2)垃圾收集机制
新生代和老年代采用的是不同的垃圾收集机制
a.新生代特点是朝生夕死。采用的复制算法,即可用的内存平均分为2块,每次只用一块,gc时,将使用的那一块存活的对象复制到另一块中,然后原有块空间一次清理掉。
b.年老代特点是生存周期比较长。采用的是标记整理算法或标记清除算法,标记清除算法是把要回收的对象进行标记,然后再把它清理掉,而标记压缩算法则是把所有存活的对象向一端移动,然后清理掉边界以外的空间。
(3)常用垃圾收集器和优缺点
垃圾收集器比较多,只列举典型的
Serial收集器:单线程收集器,简单高效,cpu利用率高,等待时间久
Parellel Scavenge收集器:多线程,停顿时间和吞吐量之间要平衡
CMS收集器:响应优先,停顿时间短
G1收集器:并行与并发、分代收集、空间整合、可预测停顿
3.java内存模型
(1)三大原则
原子性:基本的操作,比如read、load、assign、write、save等原子性的
有序性:单线程的执行结果是确定的,不会改变,多线程无此要求
先行原则:如果一个操作先行于另一个操作可见,必然先行于另一个操作
(2)volatile
用volatile修饰的变量,读取操作要从主内存直接读取最新的值,赋值操作完成之后要刷到主内存当中,该原理是通过插入内存屏障指令,由CPU原生就支持
(3)synchronized
和volatile所不同的是,代码块整个从主内存读取,然后解锁之前刷新到主内存,实现原理则是监控锁
(4)锁优化
a.自旋锁
短时间做空操作,没有获取锁则再阻塞
b.锁消除
如果一段代码确定不会被多个线程同时访问,则去掉锁
c.锁粗化
如果加锁和解锁频繁或者干脆在循环里,则考虑把锁粗化
d.轻量锁
通过CAS操作更新Mark Word上的锁状态,如果没有更新成功,则检查是否是当前线程获取锁,如果还不是,则阻塞
e.偏向锁
通过CAS操作获取了锁,如果下次还是同一个线程,则不再执行锁同步,直接执行代码块,如果不是,偏向锁失败,则阻塞
4.JIT技术
当某个方法通过计数的方式,当次数超过一定阈值时,会将其编译为本地码(时间会比较长),当下次再调用该方法时,直接调用本地码。
七、卸载
虽然说有人把方法区称作为永久区,但实际上还是有可能被回收掉的,当某个类确定不再被使用时,可以从方法区中卸载

参考文档:
1.《深入理解java虚拟机》