《深入理解java虚拟机》读书笔记(一)---- 类加载机制

类加载的时机

1、类从虚拟机加载到内存开始,到卸载出内存为止,整个生命周期分为七个阶段:加载、验证、准备、解析、初始化、使用和卸载。其中验证、准备和解析统称为连接阶段。

2、加载、验证、准备、初始化和卸载这五个阶段是按顺序执行的,而解析阶段却不一定,解析可以在初始化之后执行。

3、初始化阶段,有且只有五种情况必须对类进行初始化:
①遇到new、getstatic、putstatic和invokestatic这四个字节码时,如果类没有进行过初始化,则必须要记性初始化。这四种场景分别对应着对则用new关键字对类进行实例化,设置或读取一个静态对象(用final修饰的静态常量已经在编译器放到了常量池,所以除外),和调用静态方法。
②对类进行了反射调用的时候。
③初始化一个类时,如果其父类没有被初始化,则必须初始化其父类。
④程序主类(main()方法) 必须初始化。
⑤当使用jdk7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄时,并且这个方法句柄对应的类没有初始化,则必须要先初始化。

4、除此之外类的所有引用都不会初始化,称为被动引用,例子:
①通过子类引用父类的静态字段,只会对父类初始化,子类不初始化。
②通过数组定义来引用类,不会初始化该类。
③常量在编译期汇存入调用类的常量池中,不会触发定义类的初始化。

类加载的过程
1、加载

(1)在加载过程中完成三件事:
①通过类的权限定名来获取定义此类的二进制字节流。
②将这个字节流代表的静态存储结构转化为方法区的运行时数据结构。
③在内存中生成一个代表此类的Object对象,作为方法区代表此类的访问入口。

(2)因为虚拟机规范中并没有明确指定这个二进制字节流要从哪获取,使用使得很多java技术都建立在这基础之上:
①从zip包中读取,最终演变成了jar, ear, war格式的基础。
②从网络中获取,代表的场景有applet。
③java的动态代理技术。
④由其他文件生成,比如用jsp文件去生成。
⑤从数据库中读取。

2、验证

验证阶段是虚拟机对自身的保护的一项重要工作。
(1)文件格式验证,验证自己留是否符合class文件格式规范,并且能被当前版本的虚拟机处理。
①是否以魔数0xCAFEBABE开头。
②主次版本号是否在当前版本处理范围内。
③常量池的常量中是否有不被支持的常量类型。
④指向常量的各种索引值中是否有指向不存在的常量或不存在的常量类型。

(2)元数据验证,对字节码描述的语义进行分析,以保证符合java的规范。
①这个类是否有父类(除了java.lang.Object类)。
②这个类的父类是否继承了不被允许继承的类(被final修饰的类)。
③如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法。
④类中的字段、方法会与父类矛盾。

(3)字节码验证,通过数据流和控制流分析,确定程序的合法性和是符合逻辑的。

(4)符号引用验证,虚拟机将符号引用转化为直接引用,对类以外的星系进行匹配性校验。
①符号引用中通过字符串描述的全限定名是否能扎到类。
②在指定类中是否存在符合方法的字段描述以及简单名称所描述的方法和字段。
③符号引用中的类、字段和方法的访问性是否可被当前类访问。

3、准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中分配。这里的只包括类变量而不包括实例变量,而且这里的初始化也是指相应数据类型的零值,赋具体值的操作将会在初始化阶段进行。

数据类型零值
int0
long0L
short(short)0
char‘\u0000’
byte(byte)0
booleanfalse
float0.0f
double0.0d
referencenull

但是要注意,有final修饰的变量会直接赋具体值。

4、解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
(1)类或接口的解析:假设当前代码所处的类为D,如果要把一个从未解析过的符号引用N解析为一个类或接口C的直接引用,那么虚拟机完成整个过程需要以下三个步骤:
①如果C不是一个数组类型,那么虚拟机将会把代表N的全限定名传递给D的类加载器去加载这个类C。
②如果C是一个数组类型,并且数组的元素类型为对象,那么就会按照①中的方法去加载数组元素类型。
③如果上面的步骤没有异常,那么C在虚拟机中实际上已经成为一个有效的类或接口了,但是在解析完成之前还是要进行符号引用验证,确定D是否具备对C的访问权限。
(2)字段解析
(3)类方法解析
(4)接口方法解析

5、初始化

类初始化是类加载过程的最后一步,真正执行类中定义的程序代码。是执行类构造器 < clinit>()方法的过程。

  • < clinit>()方法是由编译自动收集类中所有变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在他之后的变量,在前面的静态语句块可以赋值,但是不能访问。
  • < clinit>()方法与类的构造函数不同,它不需要显式的调用父类构造器,虚拟机会保证在子类的< clinit>()方法执行之前,父类的< clinit>()方法已经执行完毕。因此虚拟机中第一个执行< clinit>()方法的类一定是java.lang.Object。
  • 由于父类的< clinit>()方法先执行,所以父类的静态语句块一定先于子类的静态语句块执行。
  • < clinit>()方法对于类或接口来说并不是必须的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成< clinit>()方法。
  • 解耦中不能使用静态语句块,但任然会有变量的初始化赋值操作,所以接口与类一样都会生成< clinit>()方法。但接口与类不同的是,执行接口的< clinit>()方法不需要先执行父接口的< clinit>()方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会直接接口的< clinit>()方法。
  • 虚拟机会保证一个类的< clinit>()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的< clinit>()方法,其他线程会阻塞等待,直到活动线程执行< clinit>()方法完毕。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值