类的生命周期包含了如上的7个阶段,其中 验证 、 准备 、 解析 统称为 连接,类的加载主要是前五个阶段,每个阶段基本上保持如上顺序 开始(仅仅是开始,实际上执行是交叉混合的),只有 解析 阶段不一定,在 初始化后 也有可能才开始执行解析,这是为了支持动态语言。
加载
加载就是将字节码的二进制流转化为 方法区 的运行时数据结构,并生成类所对象的Class对象,字节码二进制流可以是我们编译后的class文件,也可以从网络中获取,或者运行时动态生成(动态代理)等等。
那什么时候会触发类加载呢?这个在虚拟机规范中没有明确定义,只是规定了何时需要执行初始化(稍后详细分析)。
### 类加载器与双亲委派模型详解
#### 类的生命周期与加载过程
类的生命周期主要包括七个阶段:加载、验证、准备、解析、初始化、使用和卸载。在这七个阶段中,验证、准备和解析统称为连接阶段。类的加载过程主要涉及前五个阶段。
- **加载**:此阶段的主要任务是将字节码文件读入内存,并将其转化为方法区的运行时数据结构。同时,创建`Class`对象以作为方法区中这些数据的访问入口。字节码文件既可以是传统的`.class`文件,也可以是动态生成的代码或者是从网络下载的。
- **验证**:这一阶段的目的是确保字节码文件符合Java虚拟机的要求。验证过程包括文件格式验证、元数据验证、字节码验证和符号引用验证四个步骤。
- **准备**:在这个阶段,类变量(即静态变量)被分配内存并初始化为默认值,而非程序中指定的初始值。
- **解析**:将符号引用转换为直接引用的过程,即找到类定义中的具体内存地址。
- **初始化**:最后一步,执行类构造器`<clinit>`方法,执行静态初始化器和赋值操作。
#### 类的加载时机
虽然虚拟机规范并没有明确指出类应该在什么时间加载,但它规定了类初始化的时间点。初始化通常会在以下几种情况下发生:
- 使用`new`关键字实例化对象。
- 访问或修改一个类的静态字段(除非该字段已被编译器优化为常量并存储在常量池中)。
- 调用一个类的静态方法。
- 使用反射机制来强制加载某个类。
- 初始化一个类的子类时,如果父类尚未初始化,则会先初始化父类。
- 当JVM启动时被标明为启动类的类会先被初始化。
#### 双亲委派模型
在Java中,类加载机制采用了一种叫做“双亲委派”的策略。这种模型由三个内置的类加载器组成:Bootstrap ClassLoader(引导类加载器)、Extension ClassLoader(扩展类加载器)和App ClassLoader(应用程序类加载器)。
- **Bootstrap ClassLoader**:这是系统最基本的类加载器,负责加载Java的核心类库,如`java.lang`、`java.util`等,这些类位于`rt.jar`中。
- **Extension ClassLoader**:扩展类加载器负责加载扩展目录下的类库,默认路径为`JAVA_HOME/lib/ext`目录中的jar包。
- **App ClassLoader**:应用程序类加载器,负责加载用户自定义的类路径中指定的类,是最常见的类加载器。
当一个类加载请求到来时,请求会被传递给父类加载器处理,只有当父类加载器无法处理时,才会由自身处理。这样做的好处在于:
- 避免了类的重复加载,提高了系统的稳定性和安全性。
- 提供了类加载的安全隔离,防止恶意代码破坏核心类库的功能。
#### 打破双亲委派模型
虽然双亲委配模型提供了很好的安全性和稳定性,但在某些特定情况下可能需要打破这一模型。例如,当需要在不同的类加载器之间共享资源时,或者当需要动态生成类时(如使用`java.lang.instrument`包中的工具),就需要绕过双亲委派模型。这种方式常见于Spring框架中的AOP实现以及其他一些动态代理技术中。
Java中的类加载机制是十分复杂的,涉及到多个阶段和多种类加载器之间的交互。正确理解和掌握这些机制对于开发高效稳定的Java应用至关重要。