本篇文章主要内容是对java虚拟机进行一定的讲述,因为jvm是java许多特性以及实现的基础,所以有利于对java特性实现原理
有一定的掌握;当然,其实这也只是相当于《深入理解java虚拟机》的读书笔记而已
jvm整体架构
(1)类加载机制
(2)运行时数据区:方法区;虚拟机栈;本地方法栈;堆;程序计数器
(3)执行引擎
(4)垃圾收集器
类加载机制
(1)类加载过程
加载:通过类名获取二进制字节流
将字节流转化为方法区的运行时数据结构
方法区生成Class对象作为类数据访问入口
验证:对字节流数据验证,保证其符合Class文件格式的约束
准备:为类变量分配内存并设置类变量初始值
解析:将常量池内符号引用转化为直接引用
初始化:通过执行<clinit>方法完成初始化
注:<clinit>方法包括类变量赋值操作和静态代码块
运行时数据区
(1)方法区--用于存储虚拟机加载类信息,运行时常量池
(2)虚拟机栈--用于描述java方法执行的内存模型,每次方法执行会创建栈帧
栈帧:包括局部变量表,操作数栈,帧数据区等
(3)本地方法栈--相似于虚拟机栈,用于保存native方法信息
(4)堆--用于存放对象实例,是线程共享的
(5)程序计数器--记录正在执行的虚拟机字节码指令地址,是线程私有的
注:java虚拟机对象创建
(1)当虚拟机遇到new指令时,首先检查类信息是否被加载,没有加载则
通过类加载器进行加载操作
(2)接着读取类信息对对象进行分配内存并初始化为0,设置对象头信息
(3)执行init方法对对象进行初始化操作
栈帧数据结构
(1)局部变量表
存放方法参数和方法内部定义的局部变量,而变量槽slot为局部变量表容量的最小单位,
注意变量空间何时会被释放
(2)操作数栈
即方法执行过程中通过各种字节码指令对操作数栈进行入栈和出栈操作,而两个
栈帧之间可能会出现优化效果,即出现操作数栈和局部变量出现部分重叠
(3)动态连接
将符号引用转为直接引用的过程,其中在类加载过程进行加载称为静态解析,而
在运行时进行加载称为动态连接
(4)方法返回地址
包括正常退出和异常退出,而正常退出时则将pc计数器的值作为返回地址,而异常
退出时则通过异常处理器表确定
(5)附加信息
class文件数据结构
(1)magic(魔数)--占头四个字节
0xCAFEBABE----用于分辨class文件和非class文件
(2)minor_version(次版本号是第5和第6字节)和major_version(主版本号是第7和第8字节)
注:可以尝试使用十六进制编辑器打开class文件
(3)常量池(常量池是一个可变长度cp_info表的有序序列)
概述:存放两大类常量:字面量和符号引用,注意符号引用包括"类和接口的全限定名"
"字段的名称和描述符"和"方法的名称和描述符"
常量池内常量都为表结构,共有14种表结构
注:表结构的第一位是u1类型的标志位,代表是哪种表类型
<1>CONSTANT_UTF8_info表:UTF编码的字符串
<2>CONSTANT_Integer_info表:
<3>CONSTANT_Float_info表
<4>CONSTANT_Long_info表
<5>CONSTANT_Double_info表
<6>CONSTANT_Class_info表:类或接口的符号引用
<7>CONSTANT_String_info表
<8>CONSTANT_Firlfref_info表:指向字段的符号引用
<9>CONSTANT_Methodref_info表:使用符号引用表述类中声明的方法
<10>CONSTANT_InterfaceMethodref_info表:表述接口中声明的方法
<11>CONSTANT_NameAndType_info表:指向字段和方法的符号引用
<12>CONSTANT_MethodHandle_info表:表示方法句柄
<13>CONSTANT_NameAndType_info表:标识方法类型
<14>CONSTANT_InvokeDynamic_info表:表示一个动态方法调用点
注:即java内部是将数据通过类型进行划分而不是类,注意每种表内部存储的数据
和数据的最大值
(4)访问标志(两个字节)
ACC_PUBLIC:是否是public类型
ACC_FINAL:是否声明为final
ACC_SUPER:是否允许使用invokespecial字节码指令
ACC_INTERFACE:标识这是接口
ACC_ABSTRACT:是否为abstract类型
ACC_SYNTHETIC:标识这个类不是由用户代码产生的
ACC_ANNOTATION:标识这是注解
ACC_ENUM:标识这是枚举
(5)类索引,父索引和接口索引集合
(6)字段表集合
用于描述接口或类声明的变量,包括类级变量和实例级变量,通过字段表结构
进行存储,包括access_flags(字段修饰符),name_index,descriptor_index
(7)方法表集合
和字段表类似,但是方法内的代码存放在Code属性内
(8)属性表集合:
表示某些地方特有的信息,例如方法表内的Code属性
执行引擎
(1)方法调用
解析--将方法的符号引用转化为直接引用
分派
静态多分派--在编译期根据参数的静态类型实现重载
动态单分派--在运行期根据实际对象类型确定方法
注:虚方法表--存放各个方法实际入口地址以提高元数据查找性能
(2)字节码解释执行--通过程序计数器,局部变量表,操作栈实现指令的解释执行
垃圾回收机制--回收执行代码已经不再引用的对象所占内存
对象回收条件
(1)可达性分析算法:
当对象到达GC Roots没有引用链相连,即对象不可达时证明对象不可用
GC Roots:指帧栈所引用对象或方法区类静态属性引用对象
(2)判断对象死亡过程:
(1)先通过可达性分析算法判断对象是否可达,不可达则进行第一次标记过程,
(2)然后检查对象是否覆盖finalize()方法或没调用过finalize()方法,
如果其中有为否则直接进行GC,反之加入F-Queue队列中,由Finalizer执行
(3)如果对象在F-Queue队列中没有执行finalize()方法则被GC
(3)方法区垃圾回收--用于回收废弃常量和无用的类
类信息回收条件--不存在类实例且ClassLoader被回收,且Class对象没有反射调用
垃圾回收算法及其实现
(1)垃圾收集算法--标记所有需要回收的对象并在标记完成后进行统一回收
复制算法--将内存分为一块较大的Eden空间和两块较小的Survivor空间并
将存活对象复制到Survivor空间再清空其余内存空间
标记-整理算法--将存活对象向一端移动并清理其余内存空间
分代收集算法--将对象区分为新生代和老年代并使用不同算法进行收集
新生代:对象存活时间短;
老年代:对象存活时间长且占用堆空间较大
(2)垃圾收集器
serial收集器:单线程垃圾收集器,主要作用于桌面级新生代
parnew收集器:多线程serial收集器,主要作用于server新生代
parallel scavenge收集器:使用复制算法的多线程收集器,
目标是达到可控的吞吐量
serial old收集器:使用标记-整理算法的单线程收集器
parallel old收集器:实现目标是达到可控吞吐量的多线程收集器
cms收集器:基于标记-清除算法的并发垃圾收集器
g1收集器:面向服务端应用的垃圾收集器,具有
并行与并发,分代收集,空间整合,可预测停顿的特点