Java 类加载机制
Java 类加载机制探究。
类的加载、链接和初始化
系统有可能在第一次使用到某个类的时候加载某个类。也有可能采用预加载机制来加载某个类。
JVM 和类
不管一个 Java 程序如果复杂,都是运行在 JVM 上,而一个 JVM 就是操作系统中的一个 JVM 进程。
JVM 进程的中止可能有一下几种情况:
(1) 程序运行到最后正常结束。
(2)程序运行到使用 System.exit()
或 Runtime.getRUntime().exit()
代码处结束程序。
(3)程序执行过程中遇到未捕获的异常或错误而结束。
(4)进程从外部被终止,比如人为结束进程,或者操作系统结束进程。
类的加载
类的加载指的就是将类的 .class
文件读入内存,并为之创建一个 java.lang.Class
对象。
类的加载是由类加载器来完成的。类加载器通常由 JVM 提供,但是程序员也可以自定义类加载器。JVM 提供的类加载器通常称为系统类加载器。
类的连接
TODO
类的初始化
JVM 初始化一个类包含一下几个步骤:
(1)假如这个类还没有被加载和连接,则程序先加载并连接该类。
(2)假如该类的直接父类还没有被初始化,则先初始化其直接父类。
(3)假如类中有初始化语句,则系统依次执行这些初始化语句。
当执行第 2 个步骤的时候,系统对直接父类的初始化也遵循这三个步骤。如果该直接父类又有直接父类,则系统再次重复这三个步骤来先初始化这个父类,以此类推。所以 JVM 最先初始化的总是 java.lang.Object
类。
其中第 3 步中的初始化语句的执行顺序为:
(1)父类的静态代码块或父类的静态成员变量(类变量),按在代码中出现的先后顺序执行。
(2)当前类的静态代码块或当前类的静态成员变量(类变量),按在代码中出现的先后顺序执行。
(3)父类的普通代码块或父类的普通成员变量,按在代码中出现的先后顺序执行。
(4)父类的构造函数。
(5)当前类的普通代码块或当前类的普通成员变量,按在代码中出现的先后顺序执行。
(6)当前类的构造函数。
参考:Initializing Fields - The Java™ Tutorials - Oracle
类初始化的时机
6种类初始化的时机。
- 创建类的实例。
- 调用某个类的类方法(静态方法)。
- 访问某个类或接口的类变量,或为该类变量赋值。
- 使用反射方式强制创建某个类或者接口对应的
java.lang.Class
对象。 - 初始化某个类的子类。这个子类的父类也都会被加载。
- 直接使用
java.exe
来运行某个主类,会先初始化该主类。
但是有一些特殊情况:
(1)对于一个 final
型的类变量(静态变量),如果该类变量的值在编译时就可以确定下来,那么这个类变量相当于“宏变量”。Java 编译器会在编译时直接把这个类变量出现的地方替换成它的值,因此即使其他类中使用 类名.静态变量
的形式使用这个类变量,这个类也不会被加载。因为编译的时候,所有用到 类名.静态变量
的地方都已经替换成实际的值了。
比如,对于下边这段代码:
1 | class Test{ |
虽然 Demo
类中使用到了 Test.var1
,但是 Demo
类中用到 Test.var1
的地方在编译时就被替换成实际值 testString
了。所以在用到 Demo
类的时候并不会导致 Test
类的加载。
注意这里的条件是“该类变量的值在编译时就可以确定下来”,必须满足这个条件才可以。
小结
注意类的加载和类的初始化是需要进行区分的。
类加载器
常见问题
参考资料
- 《疯狂 Java 讲义》第3版 第18章. 类加载机制与反射
- Jvm 系列(一):Java 类的加载机制 - 纯洁的微笑博客
- 老大难的 Java ClassLoader 再不理解就老了 - 知乎
- JVM 类加载机制详解 - ImportNew
- Chapter 4. The class File Format - Java Virtual Machine Specification
- [一]class 文件浅析 .class文件格式详解 字段方法属性常量池字段 class文件属性表 数据类型 数据结构
- Java: Which of multiple resources on classpath JVM takes? - Stack Overflow【同名资源的加载顺序】
- Java依赖冲突高效解决之道 - 墨天轮
- 如何实现Java类隔离加载?-阿里云开发者社区
- Java 类隔离加载的正确姿势 - 知乎