前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM中类加载的时机

JVM中类加载的时机

作者头像
用户4919348
发布2019-04-02 11:08:50
4300
发布2019-04-02 11:08:50
举报
文章被收录于专栏:波波烤鸭波波烤鸭波波烤鸭

  本文主要记录下类的加载过程,一个类从加载到内存中开始到被卸载的整个生命周期作为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);
    }
}

输出结果

SuperClass init!
666
  1. 通过数组定义来引用类,不会触发类的初始化

  案例直接用1中的类结构

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

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

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

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

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

参考《深入Java虚拟机》

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年03月09日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 类的生命周期
  • 类的加载时机
    • 被动引用的例子
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档