写在前面

本文隶属于专栏《100个问题搞定Java虚拟机》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!

本专栏目录结构和文献引用请见100个问题搞定Java虚拟机

解答

JVM加载java类就是将字节流(如.class文件,网络传输的字节流)文件加入到内存中的过程,分为以下三步:加载、链接、初始化
1. 加载是指查找字节流,并且据此创建类的过程。
   加载需要借助类加载器,在 Java 虚拟机中,类加载器使用了双亲委派模型,即接收到加载请求时,会先将请求转发给父类加载器。
2. 链接,是指将创建成的类合并至 Java 虚拟机中,使之能够执行的过程。
   链接还分验证、准备和解析三个阶段。其中,解析阶段为非必须的。
3. 初始化,则是为标记为常量值的字段赋值,以及执行 <clinit> 方法的过程。
类的初始化仅会被执行一次,这个特性被用来实现单例的延迟初始化。

补充

加载

Java 将字节码数据从不同的数据源读取到 JVM 中,并映射为 JVM 认可的数据结构(Class 对象)。

这里的数据源可能是各种各样的形态,如 jar 文件、class 文件,甚至是网络数据源等;

如果输入数据不是 ClassFile 的结构,则会抛出 ClassFormatError。

加载规则

双亲委派机制

详情请见我的另一篇博客——双亲委派模型是什么?

类的唯一性

类加载器名称+类全限定名称

链接

这是核心的步骤,简单说是把原始的类定义信息平滑地转化入 JVM 运行的过程中。

验证

这是虚拟机安全的重要保障,JVM 需要核验字节信息是符合 Java 虚拟机规范的,否则就被认为是 VerifyError,这样就防止了恶意信息或者不合规的信息危害 JVM 的运行,验证阶段有可能触发更多 class 的加载。

准备

创建类或接口中的静态变量,并初始化静态变量的初始值。

但这里的“初始化”和下面的显式初始化阶段是有区别的,侧重点在于分配所需要的内存空间,不会去执行更进一步的 JVM 指令。

解析

将符号引用解析为实际引用, 符号引用是在编译阶段由编译器生成,包含目标方法所在类的名字、目标方法的名字、接收参数类型以及返回值类型

初始化

这一步真正去执行类初始化的代码逻辑,包括

  1. 静态字段赋值的动作
  2. 执行类定义中的静态初始化块内的逻辑

编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。

具体来说就是为标记为常量值的字段(基本类型或字符串且被修饰为final)赋值,以及执行方法(其他赋值操作和静态代码块)。

类的初始化过程是线程安全的,并且只能被初始化一次。jvm会通过加锁来保证方法仅被执行一次。

初始化的时机

对一个类的主动引用

被动引用并不会引发类的初始化,如引用类的静态常量,引用父类的静态字段不会初始化子类,数组定义来引用类不会导致初始化。

JDK9

在 JDK 9 中,由于 Jigsaw 项目引入了 Java 平台模块化系统(JPMS),Java SE 的源代码被划分为一系列模块。

平台类加载器(Platform Class-Loader)

  1. 扩展类加载器被重命名为平台类加载器(Platform Class-Loader),而且 extension 机制则被移除。
    也就意味着,如果我们指定 java.ext.dirs 环境变量,或者 lib/ext 目录存在,JVM 将直接返回错误!
    建议解决办法就是将其放入 classpath 里。

  2. 部分不需要 AllPermission 的 Java 基础模块,被降级到平台类加载器中,相应的权限也被更精细粒度地限制起来。

  3. rt.jar 和 tools.jar 同样是被移除了! JDK 的核心类库以及相关资源,被存储在 jimage 文件中,并通过新的 JRT 文件系统访问,而不是原有的 JAR 文件系统。
    虽然看起来很惊人,但幸好对于大部分软件的兼容性影响,其实是有限的,更直接地影响是 IDE 等软件,通常只要升级到新版本就可以了。

  4. 增加了 Layer 的抽象, JVM 启动默认创建 BootLayer,开发者也可以自己去定义和实例化 Layer,可以更加方便的实现类似容器一般的逻辑抽象。

结合了 Layer,目前的 JVM 内部结构就变成了下面的层次,内建类加载器都在 BootLayer 中,其他 Layer 内部有自定义的类加载器,不同版本模块可以同时工作在不同的 Layer。

Java平台模块化系统

上一篇 下一篇