Class Loading in Java

date
Jan 12, 2024
slug
Java-class-loading
status
Published
tags
Java
summary
type
Post

Java Program Execution Lifecycle

notion image
当我们写好一个 Java 程序,在 IDE 中点击运行时,它是如何被运行的?首先 Java 源代码经过 javac 编译为字节码,每个类会生成一个对应的 class 文件;在我们第一次使用某个类时,类加载器会将这个类以及相关的类的字节码加载进入 JVM,JVM 解释执行字节码(当然还包括了 JIT);因此,总结来说,Java 程序的执行周期可以概括为:编译,类加载,执行。

Runtime Type Identification(RTTI)

在编写 Java 程序时,有时我们希望在运行时获取某个对象的实际类型信息(往往是在使用多态的时候我们希望对某些子类做特定处理),在 Java 中可以通过获取对象所属类型的 `Class` 对象来实现,可以通过定义在 Object 类中的 getClass()来得到 Class 对象。每一个类都持有其对应的Class类的对象的引用,其中包含着与类相关的信息。
而 Class 对象的来源正是其代表的类对应的字节码文件,其中包含着类型信息。
JVM 运行代码的第一步,就是将要使用的类的字节码加载到 JVM 内存中,这个工作是由 ClassLoader 类的对象来完成的。

Java Default ClassLoader

前文提到,要使用一个类需要先加载其对应的字节码,但是 ClassLoader 本身也是一个 Java 类,因此 Java 提供了一个以机器码形式存在的 Bootstrap ClassLoader,用来加载 Java 的核心类,比如 rt.jar 和 $JAVA_HOME/jre/lib 下的 Java 核心类。
此外,Java 还提供了一个 Extension/Platform ClassLoader 来加载一些平台相关的,不属于标准 Java 核心库的类,比如那些在 $JAVA_HOME/lib/ext 中的类。
而 System/Application ClassLoader 用来加载那些应用级别的类,比如那些在 classpath 所指定的路径下的类。

When the JVM requests a class?

  • 第一次引用静态成员(构造函数也属于静态方法,因此也是引用静态成员)
  • java.lang.Class.forName()
  • 总结来说就是你需要用到类型信息的时候就会加载。

How does ClassLoader Work?

notion image
类加载器的工作方式可以用一种称为双亲委派模型的方式来描述,当收到对某个类的类加载请求时:
  • 首先将请求委派给父加载器处理(这里的父并不指继承关系,仅指在双亲委派模型中的层级关系)。
  • 若父加载器找到,则加载之后将结果传回给子加载器。
  • 若父加载器找不到,或者没有父加载器,则自己处理,在自己负责的位置寻找类对应的字节码文件。
  • 若最终仍然找不到,则抛出异常。

Consequences of the Delegation Model

委派模型带来的两个结果是:
  • 类的唯一性:只有父加载器找不到的类子加载器才会自己加载,因此由不同加载器加载进来的类都是不同的。
  • 可见性:子加载器可以看见父加载器加载的类,而反过来不行。这也就避免了用户自己写一个类恶意替换掉某些 Java 核心类,比如 String.

Customize ClassLoader

开发者可以通过继承 ClassLoader 类来自定义 Application 级别的类加载器。
但是,这有什么用?
  • 需要修改现有字节码的场景:比如热部署。
  • 加密:核心代码不想公开,但是又需要使用,可以将加密后的字节码交给类加载器加载,再采用某种解密算法将真正的类载入JVM,保证核心代码不被反编译泄漏。
  • 需要从本机硬盘之外的地方加载字节码:比如网络。

Summary

  • 所有的类都是在对其第一次使用时,动态加载到JVM中去的。
  • 当程序创建第一个对类的静态成员的引用时,JVM会使用类加载器来根据类名查找.class文件。
  • 一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象
 

© Lifan Sun 2023 - 2024