走进 JVM 之字节码与类加载

本贴最后更新于 1518 天前,其中的信息可能已经斗转星移

cafebabe.png

字节码

JVM 主流还是 HotSpot,是 OpenJDK 最主流的一种。

跨平台需要一个中间层,那就是字节码(ByteCode),JVM 会将字节码编译执行,热点代码还会通过 JIT 动态编译成机器码提高效率。

一个 Java 类文件,用十六进制表示二进制流。其中,起始的 4 个直接非常特殊:cafe babe。是 Gosling 定义的一个魔法数,标志该文件是一个 Java 类文件。

JVM 在字节码上设计一套操作码助记符,用特殊的单词来标记。

一个 .java 文件转换成字节码甚至是机器码文件需要以下几步:

Java ==> 词法解析 = token 流 => 语法解析 ==> 语义分析 ==> 生成字节码 ==> 字节码

其中,语法解析的目的是组装成一颗语法树,再通过 语义分析 阶段检查类型、关键字、作用域是否合规。

字节码必须通过类加载过程加载到 JVM 环境后才可以执行。执行有 3 种模式

  • 解释执行
  • JIT 编译执行
  • JIT 编译执行与解释混合执行

主流的 JVM 就是混合执行,在控制台输入 java -version 也可以看到 mixed mode 的字样,表示是混合执行。

类加载过程

类是在运行期间第一次使用时动态加载的,而不是一次性加载所有类。因为如果一次性加载,那么会占用很多的内存。

流程

主要分成三大步

  1. 加载

    只是类加载的第一个阶段,不要和类加载混淆。主要干三件事情:

    • 通过类的完全限定名称获取定义该类的二进制字节流。
    • 将该字节流表示的静态存储结构转换为方法区的运行时存储结构。
    • 在内存中生成一个代表该类的 Class 对象,作为方法区中该类各种数据的访问入口。
  2. 链接(验证、准备、解析)

  3. 初始化

    分为主动引用和被动应用。

    • 主动引用:

      new 关键字、main 类等

    • 被动引用:

      静态字段、常量、SuperClass[] sca = new SuperClass[10]; 这样一个数组的初始化。

      通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法。

初始化的过程:读取字节码的二进制数据到内存中,在 JVM 的方法区内,然后利用字节码文件创建一个 Class 对象作用在堆区。

所以说类加载是一个将.class 字节码文件实例化成 Class 对象并进行相关初始化的过程。

类加载器

双亲委派模型,或者叫「溯源委派加载模型」更合适。因为类加载器类似原始部落,存在权利等级制度,最高的一层是 BootStrap。

低层次的当前类加载器,不能覆盖更高层的加载器加载的类。所以低层的加载器想要加载某个类时,需要向上逐级询问:该类加载了吗?高一级的父加载器往往都是懒狗,收到下级的请求会转发该请求给自身的上级。

所以一次询问一定会传递到 BootStrap ClassLoader。加载不到时,才会逐级向下尝试加载。如果父类都加载不了,就会允许当前类加载器加载,不然肯定是优先父类加载器加载。

所以是按需加载。

父子关系是通过组合关系实现,非继承关系。

这个模型的好处:

  1. 避免重复加载
  2. 安全性问题,防止核心的加载器已加载的类被「覆盖、篡改」

例子:

比如在自己开发的环境中,不要定义和核心API同名的包名。

1.根加载器(Bootstrap)

最底层的加载器,由 C++ 实现,没有父加载器所以没有继承 java.lang.ClassLoader,负责将存放在 <JRE_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的。

负责装载最核心的类:Object、System、String 等。

根类加载器加载的类,打印他的 Classloader 只会是 null,因为不是在 JVM 体系内。

例如:System.out.println(Object.class.getClassLoader());

2.扩展类加载器(Platform)

纯 Java 语言编写。用于加载一些扩展的系统类(也就是安全性和重要性比上者稍差一点),比如:XML、加密、压缩。

这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。

Java9 之前是 ExtClass,之后是 PlatformClass。

3.应用类加载器(Application)

这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。

负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器。比如自己写的 TestFuck 类,该类就是被该加载器所加载。

4.自定义加载器

ClassLoader

该类和 Class 都位于 java.lang 这个包下,主要是对双亲委派模型的实现。

loadClass 方法

经典递归实现

  1. 先检查当前类是否已经在当前加载器加载了,如果没有,向上请求(上级也是这个模式)
  2. 直到父类加载器抛出 ClassNotFoundException,此时尝试自己去加载。

一般不覆写该方法,因为还是要遵循双亲委派模型的机制。

findClass 方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

源码很简单,该方法需要覆写,因为上面说了,loadClass 是递归父类加载器直到抛出该异常。那么需要覆写该方法实现自己的自定义的类。

defineClass 方法

通常与 findClass 方法一起使用,在 findClass 里面调用该方法返回。

继承 ClassLoader 类,覆写 findClass 方法。

什么时候需要自定义类加载器?
  1. 隔离加载类:中间件不同的 jar 包相互影响
  2. 修改类的加载方式
  3. 扩展加载源:网络层面、数据库、电视机顶盒也能加载
  4. 防止源码泄露

5.热部署

当我们的一个类已经被加载后,通过双亲委派模型,他并不会重新加载。但是我们在编译器中开发的时候可以选择热部署的方式。他又是怎么实现的呢?

双亲委派模型的核心在于 loadClass 方法,如果直接略过或者覆写该方法,就不会出现无限向上级请求加载的情况了(递归倒了,哭 😢)。

破坏双亲委派模型的 2 种方式:

  1. 不走 loadClass 方法
  2. 将上级类加载器下本应该(交给父加载器加载)加载的 jar 包删了,这样上级就会抛出异常,直到逐级返回下级加载「虚假的类」

第二种方式有点嗯了。。

6.线程上下文加载器

我们根加载器属实位高权重,但是保不准它也有求于小弟的时候。比如 mysql-connect-java.jar,每个计科学生都知道的经典数据库驱动。

在 BootStrap 加载器下有个 rt.jar 中有个类叫做 java.sql.DriverManager,会去加载 Driver.class,而该 class 是一个接口,并没有实现类,它的实现类在第三方的 jar 包中,也就是驱动中的类,相当于反向加载了。

public Void run() {
    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

这里的 Void 是定义的一个类,不是 void 关键字。

双亲委派模型可不允许反向加载,所以这里采用的是线程上下文加载器,该方式虽然也破坏了双亲委派模型,但是更为灵活。

ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3187 引用 • 8213 回帖
  • 类加载过程
    1 引用

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...