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
对象被载入内存,它就被用来创建这个类的所有对象