Class Loading in Java
date
Jan 12, 2024
slug
Java-class-loading
status
Published
tags
Java
summary
type
Post
Java Program Execution Lifecycle

当我们写好一个 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?

类加载器的工作方式可以用一种称为双亲委派模型的方式来描述,当收到对某个类的类加载请求时:
- 首先将请求委派给父加载器处理(这里的父并不指继承关系,仅指在双亲委派模型中的层级关系)。
- 若父加载器找到,则加载之后将结果传回给子加载器。
- 若父加载器找不到,或者没有父加载器,则自己处理,在自己负责的位置寻找类对应的字节码文件。
- 若最终仍然找不到,则抛出异常。
Consequences of the Delegation Model
委派模型带来的两个结果是:
- 类的唯一性:只有父加载器找不到的类子加载器才会自己加载,因此由不同加载器加载进来的类都是不同的。
- 可见性:子加载器可以看见父加载器加载的类,而反过来不行。这也就避免了用户自己写一个类恶意替换掉某些 Java 核心类,比如 String.
Customize ClassLoader
开发者可以通过继承 ClassLoader 类来自定义 Application 级别的类加载器。
但是,这有什么用?
- 需要修改现有字节码的场景:比如热部署。
- 加密:核心代码不想公开,但是又需要使用,可以将加密后的字节码交给类加载器加载,再采用某种解密算法将真正的类载入
JVM,保证核心代码不被反编译泄漏。
- 需要从本机硬盘之外的地方加载字节码:比如网络。
- …
Summary
- 所有的类都是在对其第一次使用时,动态加载到JVM中去的。
- 当程序创建第一个对类的静态成员的引用时,JVM会使用类加载器来根据类名查找.class文件。
- 一旦某个类的
Class对象被载入内存,它就被用来创建这个类的所有对象