JVM中类加载的时机

举报
波波烤鸭 发表于 2022/03/30 00:25:08 2022/03/30
【摘要】   本文主要记录下类的加载过程,一个类从加载到内存中开始到被卸载的整个生命周期作为java程序员来说应该还是要清楚的。 类的生命周期   类从加载到内存中到卸载的整个过程中经历了如下的过程:   这...

  本文主要记录下类的加载过程,一个类从加载到内存中开始到被卸载的整个生命周期作为java程序员来说应该还是要清楚的。

类的生命周期

  类从加载到内存中到卸载的整个过程中经历了如下的过程:
在这里插入图片描述
  这几个步骤中 验证,准备,解析这三个步骤有称为连接阶段,大体的顺序是加载,验证,准备,解析,初始化,使用和卸载,前四个有部分有交叉顺序。

类的加载时机

  类加载的时机,也就是类初始化的时机(加载,验证,准备,解析)。

  1. 遇到 new ,getstatic,putstatic和invokestatic这4条指令的时候,也就是通过new关键字实例化对象,读取或者设置一个静态变量以及调用静态方法。
  2. 反射调用的时候如果没有初始化就会加载该类。
  3. 初始化子类的时候发现父类还没有被初始化就会先初始化父类。
  4. 虚拟机启动的时候,会初始化主类(含有main方法的类)
  5. 当使用JDK1.7及以上的版本中的动态语言支持时,若一个java.lang.invoke.MethodHandle实例最后的解析结果是:REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先出发它的初始化过程。

  虚拟机规范中指出有且只有这5种场景会出发初始化,并且这5种场景的行为称为对一个类的“主动引用”,除此之外所有引用类的方式都不会触发初始化,不触发初始化的也被称为被动引用

被动引用的例子

  1. 通过子类引用父类的静态变量不会初始化子类
class SuperClass {
	static {
        System.out.println("SuperClass init!");
    }

    public static int value = 666;
    public static final String JVM_TEST = "JVM TEST";
}

/**
 * 子类
 */
class SubClass extends SuperClass {

    static {
        System.out.println("SubClass init!");
    }

}

/**
 * 测试
 * @author 波波烤鸭
 * @email dengpbs@163.com
 *
 */
public class Test {
    public static void main(String[] args){
        System.out.println(SubClass.value);
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

输出结果

SuperClass init!
666

  
 
  • 1
  • 2
  1. 通过数组定义来引用类,不会触发类的初始化

  案例直接用1中的类结构

/**
 * 测试
 * @author 波波烤鸭
 * @email dengpbs@163.com
 *
 */
public class Test {
    public static void main(String[] args){
        SuperClass[] supers = new SuperClass[10];
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

  运行结果并没有打印出“SuperClass init!”,这说明并没有对SuperClass进行初始化,定义数组不会触发类的初始化

  1. 常量在编译阶段会被存入调用类的常量池中,本质上并没有直接引用到定义常量的类,所以这种场景也不会触发类的初始化
public class Test {
    public static void main(String[] args){
       // SuperClass[] supers = new SuperClass[12];
       System.out.println(SuperClass.JVM_TEST);
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

  运行结果也没有打印出“SuperClass init!”,因为虽然引用了SuperClass的常量,但其实在编译极端通过常量传播优化,已经将此常量存储到了Test类的常量池中,因Test类对此常量的引用,都会转化为Test类对自身常量池的引用了。这说明SuperClass和Test这两个类,在编译阶段完成后就没有任何关系了。

  接口的加载过程和类的加载过程步骤上是一致的,但是稍有不同的是上面的例子都是用静态语句块“static{}”来输出初始化信息的,在接口中不能使用“static{}”静态语句块。还有一个不同是:当一个类在初始化的时候,要求其父类全部都已经初始化过了,但是一个接口在初始化的时候,不要求其父接口都初始化过,只有真正使用到父接口的时候(例如:引用父接口中定义的常量)才会初始化。

参考《深入Java虚拟机》

文章来源: dpb-bobokaoya-sm.blog.csdn.net,作者:波波烤鸭,版权归原作者所有,如需转载,请联系作者。

原文链接:dpb-bobokaoya-sm.blog.csdn.net/article/details/88372788

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。