说一说JVM中的类加载机制中类的生命周期

说一说JVM中的类加载机制中类的生命周期

首先我们明确一个问题,就是在Java程序中谁需要进行类加载?
在Java中数据类型分为基本数据类型和引用数据类型,基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载

类加载的过程(类的生命周期)

Loading阶段(装载阶段)

都做了什么?
简而言之就是将Java类的字节码文件加载到机器内存中,并在内存中构建出Java对象的原型--------类模板
换句话说,查找并加载类的二进制数据,并生成对应的Class实例
在这个阶段,JVM必须完成三件事:

  • 通过类的全名获取类的二进制数据流
  • 解析类的二进制数据流为方法区内的数据结构
  • 创建java.long.Class类的实例,表示该类型,最为方法区这个类的各种数据的访问入口

linking(链接)阶段

环节一之验证(Verification)阶段:

验证阶段的目的就是保证加载的字节码是合法、合理符合规范的
验证的步骤繁多项目复杂,大体上可以分为以下四种:
格式检查: 主要包括魔数检查、版本检查、长度检查
语义检查: 是否继承final、是否有父类、抽象方法是否实现
字节码验证: 跳转指令是否指向正确的位置,操作数类型是否合适
符号引用检查: 符号引用的直接引用是否存在

环节二之准备(Preparation)阶段:

简言之就是为类的静态变量分配内存,并将其初始化为默认值
注意:

  1. 这里不包含基本数据类型被 static final 修饰的情况,因为 fianl 在编译时就会分配了,准备阶段会显式赋值
  2. 注意这里不会为实例变量分配初始化,实例变量是会随着对象一起分配到Java堆中的
  3. 在这个阶段并不会像初始化阶段中那样会有初始化或者代码执行
环节三之解析(Resolution)阶段:

就是将类、接口、字段和方法的符号引用转为直接引用

所谓解析就是将符号引用转为直接引用,也就是得到类、字段、方法在内存中的指针或者偏移量。因此,可以说,如果直接引用存在,那么可以肯定系统中存在该类、方法或者字段。但只存在符号引用,不能确定系统中一定存在该结构

不过Java虚拟机规范并没有明确要求解析阶段一定要按照顺序执行。在HotSpot VM中,加载、验证、准备和初始化会按照顺序有条不紊地执行,但链接阶段中的解析操作往往会伴随着JVM在执行完初始化之后再执行

初始化(Initialization)阶段:

在加载一个类之前,虚拟机总是会试图先加载该类的父类,因此父类的 clinit 总是在子类的 clinit 之前被调用。也就是说父类的 static 块的优先级高于子类
Java编译器并不会为所有的类产生 clinit() 初始化方法。那么哪些类在编译为字节码文件后将不包含 clinit() 方法?

  • 对于非静态的字段,不管是否进行显示赋值,否不会生成 clinit() 方法
  • 对于静态字段没有显式赋值的,不会生成 clinit() 方法
  • 对于声明为 static final 的基本数据类型的字段,不管是否进行了显式赋值都不会生成 clinit() 方法

Java程序对类的使用有两种情况:主动使用被动使用

关于主动使用的说明:
Class只有在必须要首次使用的时候才会被装载,Java虚拟机不会无条件地装载Class类型。Java虚拟机规定,一个类或接口在初次使用前,必须要进行初始化。这里指的“使用”,是指主动使用。

主动使用只有下列几种情况:

  1. 当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化。
  2. 当调用类的静态方法时,即当使用了字节码invokestatic指令。
  3. 当使用类、接口的静态字段时(final修饰特殊考虑),比如,使用getstatic或者putstatic指令。
  4. 当使用java.lang.reflect包中的方法反射类的方法时。比如:Class.forName(“com.atguigu.java.Test”)
  5. 当初始化子类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  6. 如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,该接口要在其之前被初始化。
  7. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
  8. 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。(涉及解析REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄对应的类)

关于被动使用的情况说明:
除了以上的情况属于主动使用,其他的情况均属于被动使用。被动使用不会引起类的初始化。
也就是说:并不是在代码中出现的类,就一定会被加载或者初始化。如果不符合主动使用的条件,类就不会初始化

  1. 当访问一个静态字段时,只有真正声明这个字段的类才会被初始化。
    当通过子类引用父类的静态变量,不会导致子类初始化
  2. 通过数组定义类引用,不会触发此类的初始化
  3. 引用常量不会触发此类或接口的初始化。因为常量在链接阶段就已经被显式赋值了。
  4. 调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

被动的使用,意味着不需要执行初始化环节,意味着没有()的调用。

针对代码可以设置 -XX:+TraceClassLoading 参数来追踪类的加载信息并打印出来

使用(Using)阶段:

开发人员可以在程序中访问和调用它的静态类成员信息(比如:静态字段、静态方法),或者使用new关键字为其创建对象实例。

卸载(Unloading)阶段:

在类加载器的内部实现中,用一个Java集合来存放所加载的类的引用。另一个方面,一个 Class 对象总是会引用它的类加载器,调用 Class 对象和 getClassLoader() 方法,就能获得它的类加载器。由此可见,代表某个类的 Class 类实例与其类的加载器之间为双向联系关系

一个类的实例总是引用这个类的 Class 对象。在 Object 类中定义了 getClass() 方法,这个方法返回代表对象所属类的 Class 对线的引用。此外,所有的 Java 类都有一个静态属性 Class,他引用代表这个类的 Class 对象。

一个类何时结束生命周期,取决于代表它的 Class 对象何时结束生命周期
当一个类的 Class 对象不再被引用,即不可触及时,Class 对象就会结束生命周期, Sample 类在方法区内的数据也会被卸载,从而结束一个类的生命周期

 
 
 

关于类的生命周期的分享就到这里啦 咱们下篇文章见!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值