第六章:字节码相关
class类文件结构:看文章更好,字节码部分太枯燥了,看深入理解jvm的话比较难熬,建议先看一下书,再看博客,看完博客再回头看一下书基本也就通顺了。
知乎解释
掘金解释
第七章:虚拟机类加载机制
1.类加载经过的阶段:加载、验证、准备、解析、初始化、使用、下载;其中验证、准备、解析称为;连接
- a.加载:加载是类加载过程的第一个阶段,主要做如下事情
1.根据全类名找到对应的二级制流
2.将这个流代表的静态存储结构转化为方法区的运行时的数据结构
3.在Java堆中生成一个代表这个类的对象,用来方法区中访问这些数据结构访问的入口
- b.验证:
1):文件格式验证:例如魔数,主次版本号,常量池中的常量等等是否符合规范
2):元数据验证:语义分析,父类子类,实现父类什么final方法等此类验证
3):字节码验证:最复杂的阶段,验证操作栈、跳转指令、类型转换等,这个地方jdk1.6的优化,提到了code属性表中新增的stackMapTable,由字节码验证优化成了类型的检查
4):引用类型验证:外层依赖的检查
c.准备:开始准备内存分配的对象及初始化的类型,书上解释说白了就是为类变量分配方法区中的内存和赋值 零值得过程
1.内存分配的对象 - java有两种变量,类变量和类成员变量,准备阶段只会为类变量分配内存,而类成员变量需要等到初始化阶段才开始
2.初始化的类型:在准备阶段,会为对应类变量分配该类型的零值,
例如下面的代码在准备阶段之后,sector 的值将是 0,而不是 3。
public static int sector = 3;
假如被static final修饰,那么在准备阶段就会分配为3,类变量,不会改变,初期就会赋值
d.解析:
1.将各种符号引用转变为直接引用的过程
符号引用:一组符号来描述所引用的目标,符号可以是任何形式的字面量,与虚拟机内存无关,与实现无关,因为是虚拟机规范定义的,所有的实现方式都必须一致,早已经定义好在虚拟机规范的class文件中
直接引用:可以直接指向目标的指针、偏移量或句柄等,与虚拟机内存布局是相关的,同个符号在不同虚拟机中的直接引用一般不相同
2.虚拟机规范并未限定解析的执行时间,只要求要在16个操作字节码的指令前面,另外还有加载的两种方式“
1).在虚拟机加载时候就解析
2).在使用之前进行解析
3.解析过程可以缓存,除了invokeDynamic命令
1).对于同一个符号引用加载多次是很常见的事情,所以虚拟机可以对invokeDynamic之外的命令进行缓存(运行时常量池中的记录直接引用,并标记为已解析)一次成功,后续成功;一次失败,后续失败
2).invokeDynamic是对动态命令的支持,必须等到运行时候才开始解析
4.解析类或接口、字段、类方法、接口方法为例
例1:解析类
D解析符号引用N为接口C的直接引用
a.是非数组对象,将C的全类名传递给D的类加载器,在加载解析过程会涉及到元数据验证和字节码验证,解析父类接口,假如失败就直接宣告解析失败
b.是数组,看其中元素是否为对象,是对象则执行a中步骤,如果是integer等其他形式,就由虚拟机生成对象代表数组维度和元素的数组对象
c.符号引用验证,验证权限
例2:解析字段: 解析某个类的字段:根据class_index项中索引的class_info进行解析来获取类或接口的直接引用,解析过程失败则代表本次失败,如果成功,用C代表这个类
a.类本身就包含简单名称和字段描述的字段,那么直接返回直接引用,查找结束
b.查看实现的接口,按照继承关系由下往上搜索,如果有包含了简单名称和字段描述都符合的字段,那么返回这个字段的直接引用,查找结束
c.如果这个类不是object,那么按照继承关系由下往上搜索,有符合的直接返回直接引用,查找结束
d否则查找失败,抛出noSuchMethod异常
例3:解析类方法
1.查找类或接口,用C代替
2.假如在类方法表中发现class_info是个接口,那么抛出异常
3.判断C中是否有简单名称和描述符都匹配的方法,有则返回这个方法的直接引用,解析结束
4.判断父类中是否有符合的,按照继承关系由下往上递归查找
5.判断实现的接口列表和父接口中是否有符合的,如果有,说明类C是一个抽象类,查找结束,抛出abstractMethodError异常
e.初始化:
类初始化是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户可以用自定义类加载器参与外,其他的全由虚拟机主导和控制;在准备阶段,变量已经经过一次系统要求的赋初始值,而在初始化阶段,则根据程序员通过的程序制定的主管计划去初始化类变量和其他资源,或者说:初始化节点是执行类构造器(clinit)方法的过程
1.clinit方法:
虚拟机自动收集类中的所有类变量的赋值操作和静态语句块中的语句合并产生的,顺序是在源文件中出现的顺序;
静态语句块只能访问定于在静态语句块之前的变量,在他后的,静态语句块可以赋值但是不不能访问
2.clinit方法与类的构造函数,不同,不需要显示调用父类的clinit方法,虚拟机会保证在调用子类clinit方法前调用父类的clinit方法,因此object肯定第一个执行
3.父类的clinit方法优先于子类的clinit方法,因此父类的静态代码块会优先于子类静态代码块执行
4.对于类或借口来说,clinit方法不是必须的
5.接口中也会有变量初始化的赋值操作,所以也会有clinit方法,执行接口的clinit方法不需要先执行父类的clinit方法
6.虚拟机保证多线程环境下调用clinit方法会加锁,同步;多个线程同时初始化一个类时候,只有一个线程会去执行clinit方法,其余全阻塞直到clinit结束
当执行clinit方法的线程结束后,其余线程也不会再去执行clinit方法,在同一个类加载器下,一个类型只会初始化一次,
补充:还会有实例初始化方法:构造器<init>()是指收集类中的所有实例变量的赋值动作、实例代码块和构造函数合并产生的
初始化可参考文档:
可参考:
菜鸟学习
两道面试题带你探究java类加载机制---特别不错的题目