一个Java文件的执行全部过程你确定都清楚吗?( 四 )


字符串常量池在 JDK 1.7 开始,字符串常量池就由方法区移入了堆中,字符串常量池是专门存放字符串常量的,至于为什么移入堆中,这是因为字符串的创建和对象一样频繁,销毁也就变得尤其频繁,而方法区的 GC 是伴随着 full gc 的,因为 full gc 会造成 STW,在 full gc 期间其他程序都会停止,所以都会避免 full gc,而字符串常量池放在方法区中就减少了 字符串被回收的频率,提高了 OOM 的概率 。
类加载过程在 Class 数据文件被类加载器加载到 JVM 中到编译执行,中间经历 加载、链接、初始化、使用、卸载,其中链接又分为 验证、准备、解析 。需要注意的是: 这些操作阶段不一定要等上个阶段完成后才能进行下一个阶段,解析操作往往在初始化之后再执行 。一部分验证和加载同时执行,一部分验证等到解析才会执行。下面就一个个来说明每一步的操作 。
加载通过类加载器将 Class 数据文件加载到方法区,并且在堆中创建一个 Class 对象用于访问方法区的类数据 。
验证:验证主要用于检验传来的二进制数据格式是否满足加载要求 。虽然在 java 文件的编译阶段编译器已经进行了一次检查,但是 JVM 是与前面编译器编译的过程隔开的 。
验证主要包括格式验证、语义验证、字节码验证、符号引用验证 。
1、格式验证: 与加载过程同时进行的 。 用于检验字节码模数是否正确、主版本和副版本是否在支持范围内、数据每一项是否有正确的长度等 。
2、语义验证:校验不同类之间的关系是否正确,例如是否继承了抽象类但没有实现方法,是否继承了 final 类 。
3、字节码验证:最复杂的一个验证 。从方法层面验证各个操作是否正确,比如是否会跳转到不存在的指令,函数调用是否传递正确类型的值,变量赋值是否给了正确的类型
4、符号引用验证: 发生在解析操作  。将符号验证转化为直接引用时,验证符号引用是否能正确使用 。
准备为类属性分配内存并设置零值( 这里不包括使用 static final 修饰的属性且赋值的值是一个字符串常量或一个基本数据类型常量或其他不触发方法的情况(也就是过程不会涉及构造器或者其他方法),因为字符串或者基本数据是常量,在编译时期就会分配地址,准备阶段直接就会显式初始化,而如果赋的值包括方法调用就需要在 <client> 方法里执行 ) 。如果属性值是常量,那么常量值就会在方法区中分配内存,而如果是对象,那么对象则会在堆中创建;并且实例属性参数也会跟随对象的创建在堆中,只有静态属性和对应的常量值在方法区中分配内存 。而设置的零值是当前类型的默认值,比如 private int a = 2;那么设的零值就是 0, a = 2 是在后面的<client>方法中执行的 。
解析将符号引用转成直接直接引用 。符号引用主要包括类或接口、静态属性、类方法和接口方法这四种(都是类数据,在类加载后就能获取的) 。
初始化执行静态代码块方法以及静态属性的赋值 。会将类中所有的关于类属性的赋值语句以及静态代码块中的语句收集起来集中进 <clinet> 方法中,然后执行 。 执行的顺序就是按赋值以及静态代码块的排列顺序执行 。
虚拟机在在执行 <client>方法时会加锁,使得此方法只会被一个线程加载,所以我们需要考虑类在加载时会不会发生异常死循环导致此类无法被加载
使用使用不必多说,就是调用类属性、方法 。
卸载上面说过一个类卸载所需要的条件:1、该类的所有对象都被回收;2、加载该类的类加载器被回收;3、该类对应的 Class 对象没有在任何地方被引用(无法在任何地方通过反射访问该类的方法) 。那么具体原因是什么?

一个Java文件的执行全部过程你确定都清楚吗?

文章插图
 
我们知道,对象被回收的条件是这个对象没有被引用,类也是如此,在类被加载到内存后,它会在堆中创建一个 Class 对象,并且和加载它的加载器互相关联,也就是图中的 MyClassLoader,而这个对象也和类对应的实例对象所关联,这种关联是无法切断的,而如果对应的三种变量都没有再引用,那么就相当于这个类信息没有被引用,那么也就可以被回收了 。
类被加载的场景Java 对类的使用方式分为主动使用和被动使用 。主动使用会触发类的初始化,被动使用不会(但是还是会触发初始化之前的操作) 。
主动使用的场景1、创建某个类的对象
2、调用某个类的类属性、类方法


推荐阅读