JVM|类如何正式成为可执行状态——加载、链接、初始化

3 篇文章 0 订阅

参考极客时间专栏《深入拆解Java虚拟机》

从字节码到内存中的类,先后顺序有三个:加载、链接、初始化。链接需要验证;内存中类要初始化后才能使用。

Java数据类型可分为:基本数据类型、引用数据类型。基本数据类型由JVM预先定义好了。
引用数据类型可以细分4种:类、接口、数组类、泛型参数。泛型参数会在编译过程中呗擦除,实际上JVM只有前3种引用数据类型。数组类由JVM直接生成,其余2种有对应字节流。
字节流最常见的是Java编译器生成的字节码文件。我们也可以在程序中直接生成字节流,或从网络中获取字节流。这些字节流加载到JVM中,成为类或接口(以下统称“类”)。
不论是JVM直接生成的数组类还是加载的类,JVM都要对它们进行链接和初始化。

  • 加载
    加载是指查找字节流的过程。 数组类没有对应字节流,而是由JVM直接生成。对于其他类,JVM要借助类加载器来完成查找字节流。
    Java9之前启动类加载器负责加载最基础、最重要的类,如jre/lib目录下jar包中的类(及JVM参数-Xbootclasspath指定的类)。除了启动器加载器,还有2个重要的类加载器:扩展类加载器(extion class loader)、应用类加载器(application class loader),都由Java核心类库提供。
    扩展类加载器继承自启动类加载器,负责加载通用的、相对次要的类,如jre/lib/ext目录下jar包中的类(以及由系统变量java.ext.dirs指定的类)。
    应用类加载器继承自扩展类加载器,负责加载应用程序类路径下的类(应用程序类路径指JVM参数-cp/-classpath、系统变量java.class.path或环境变量CLASSPATH所指的路径)。默认下,应用程序中包含的类由应用类加载器加载。
    Java9,引入模块系统,对类加载器进行了更改。扩展类加载器改名为平台类加载器(platform class loader)。JavaSE中除了少数几个关键模块如java.base是由启动类加载器加载外,其他模块都由平台类加载器加载。
    除了由Java核心类库提供的类加载器外,可以加入自定义类加载器,实现特定加载放时。如,对字节码文件进行加密,加载时利用自定义类加载器对其解密。
    类加载器还提供了命名空间的作用。
    JVM中,类的唯一性由类加载器实例和类的全名共同确定的。在大型应用中使用这一特性来运行同一个类的不同版本。

  • 链接
    链接指将生成的类合并至JVM中,使其能执行的过程。 可以分为3个阶段:验证、准备、解析。
    验证阶段,目的是确保类加载器能满足JVM的约束条件。
    准备阶段,目的是给被加载类的静态字段分配内存。Java代码对静态字段的具体初始化,会在稍后的初始化阶段进行。除了分配内存,部分JVM会在此阶段其他与类层次相关的数据结构,如用来实现虚方法的动态绑定的方法表。在字节码文件加载到JVM前,这个类无法知道其余类、其余类的方法、其余类的字段对应的具体地址,甚至不知道类自己的方法、字段的地址。因此,每当要引用这些成员时,Java编译器会生成一个符号引用,在运行阶段,这个符号引用一般能无歧义地定位到具体目标上。
    解析阶段,目的是将符号引用解析成实际引用。若符号引用指向未被加载的类、未被加载类的方法、未被加载类的字段,会触发该类的加载(未必触发该类的链接、初始化)。
    JVM规范未要求在链接过程中完成解析,仅规定:若某些字节码使用了符号引用,在执行该字节码前,要完成对这些符号引用的解析。

  • 初始化
    Java代码中对静态字段初始化,可以在声明时直接赋值,也可以在静态代码块对静态字段赋值。
    如直接赋值的静态字段被final修饰,且数据类型时基本数据类型或String时,则该字段会被Java编译器标记为常量值(ConstantValue),其初始化直接由JVM完成(即常量值初始化由JVM完成)。除此之外的直接赋值,及所有静态代码块中的代码,会被Java编译器放在同一个方法中,并命名为<clinit>。
    初始化指给标记为常量值的字段赋值和执行<clinit>方法的过程。 JVM通过加锁确保<clinit>方法仅被执行一次。

只有当初始化完成后,类才正式成为可执行状态。

类的初始化何时被触发? JVM规范举例情况:

  • 当JVM启动时,初始化用户指定的主类
  • 当遇到new指令时,初始化new指令的目标类
  • 当遇到调用静态方法时,初始化该静态方法所在类
  • 当遇到访问静态字段时,初始化该静态字段所在类
  • 子类初始化会触发父类初始化
  • 若接口定义了default方法,那直接实现、间接实现该接口的类的初始化,会触发该接口的初始化
  • 使用反射API对某个类进行反射调用时,初始化被调用的类
  • 当初次调用MethodHandle对象时,初始化MethodHandle指向的方法所在类
    由于类初始化是线程安全的,且仅执行一次,因此可确保多线程下有且仅有一个单例实例。

总结

类可执行态过程


ps:虚心求教。如果内容有误欢迎指出,如果内容帮助了你欢迎留下痕迹。

END

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值