类加载机制

类加载时机

类的整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载。

165816_vUKt_3171491.png

什么情况下需要开始类加载过程的第一步:加载?jvm并没有进行强制的规定。但对于初始化阶段,jvm严格规定了只有5种情况必须立即对类进行初始化(而加载、验证、准备和解析自然需要在此之前):

  1. 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时。生成这4条指令码常见的场景为:使用new实例化对象时、设置或读取一个类的静态变量(被final修饰的除外),调用类的静态方法。
  2. 使用java.lang.reflect包中的方法对类进行反射调用的时候。
  3. 当初始化一个类时,其父类还未初始化。
  4. 虚拟机启动时,需要指定要执行的主类(包含main()方法的类),jvm会先初始化这个类。
  5. 当使用jdk1.7的动态语言时,如果一个java.lang.invoke.MethodHandle实例最后的解析过程结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄时。

这5中场景称为主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。

  1. 通过子类引用父类的静态字段。
  2. 通过数组定义引用类,如Klass c = new Klass[10] 。但是会触发另外一个名为[L...Klass的类的初始化。
  3. 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类。

对于接口与类的加载有所不同,在于前述5种中的第三种:对于接口初始化时,并不要求其父接口完成初始化,只有在真正用到的时候才会初始化(如引用接口中定义的常量)。

 

 

类加载的过程

加载

  1. 通过一个完全限定名来获取定义此类的二进制字节流。
  2. 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。
  3. 在内存中生成一个代表这个类的java.lang.Class对象。作为方法区这个类的各种数据的访问入口(HotSpot将其放在方法区中)。

 

验证

  • 文件格式的验证
    • 0xCAFEBABE
    • 主、次版本号
    • 常量池中是否存在不支持的常量(检查常量flag标志)
    • 指向常量池的索引值是否存在
    • CONSTANT_UTF8_info常量是否存在不符合UTF8编码的数据
    • Class文件中各部分及文件本身是否有被删除的或附加的其他信息

...

  • 元数据验证

    • 是否有父类
    • 是否继承不允许继承的类
    • 如果不是抽象类,是否实现了父类或接口中的所有方法
    • 类中的字段、方法是否与父类发生矛盾
  • 字节码验证
    • 确定程序语义合法、符合逻辑。
    • 保证指令不会跳出方法体外
    • 保证方法体中的类型转换有效
  • 符号引用验证
    • 将符号引用转换为直接引用,这个转化过程发生在解析过程。
    • 符号引用是否能找到对应的类
    • 符号引用的类、字段、方法的访问性是否可被当前类访问
    • 验证阶段不是必须的,如果全部代码经过反复的使用和验证,可通过-Xverify:none参数来关闭大部分的类验证措施。

准备

正式为类变量分配内存并设置类变量初始值的阶段(零值),这些变量所使用的内存都将在方法区分配(仅包括类变量static)。但是特殊情况下,如类字段的字段属性表存在ConstantValue属性,那么在准备阶段会被初始化为ConstantValue属性指定的值。public static final int value = 123; 

解析

将符号引用替换为直接引用的过程。

  • 类的解析:把一个从未解析过的符号引用解析为一个类或接口C的直接引用,可能涉及C相关联的加载动作。
  • 字段解析:先从C中查找,然后是接口及其接口的父接口,按照继承关系从下往上查找父类,否则抛出异常。查找成功后验证权限。
  • 类方法:符号引用的类中的方法,父类的方法中查找。如果是接口或抽象方法则抛出异常。
  • 接口方法:

初始化

该阶段是执行类构造器<clinit>() 方法的过程。

<clinit>()方法有编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。收集的顺序是按语句在源文件中出现的顺序决定,静态语句只能访问定义在静态语句块之前的变量,定义在它后面的变量,只能赋值,不能访问。

<clinit>()与实例构造器<init>()方法不同,它不需要显示地调用父类的<clinit>(),jvm会保证子类<clinit>()方法调用前,调用父类的<clinit>()方法已经执行完毕。

<clinit>()并不是必须的。

接口中同样存在<clinit>()方法,但是不需要先执行父接口的<clinit>()。

jvm会保证一个类的<clinit>()方法在多线程环境中被正确的加锁、同步。执行<clinit>()方法的线程退出后,其他阻塞的线程不会再次进入<clinit>()方法。

 

 

类加载器

 

 

 

 

 

 

 

转载于:https://my.oschina.net/u/3171491/blog/1518475

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值