类加载过程
- 加载
- 连接
- 验证
- 准备
- 解析
- 初始化
加载
- 通过类的全限定名,找到 Class 文件,将 Class 文件中的静态存储结构转换成方法区运行时数据结构,在内存中生活才能一个代表该类的 Class 对象,作为方法区这些数据的入口
- 数组类型不通过类加载器创建,由 JVM 直接创建
连接
验证
验证字节码文件的安全性和完整性:包括:
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
准备
- 为类的静态变量分配内存和设置初始化值
- 实例变量会在对象实例化时随着对象一块分配在Java堆中。
解析
- 虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量。
初始化
- 真正执行类中定义的 Java 程序代码(字节码),初始化阶段是执行类构造器
<clinit> ()
方法的过程。 - 类初始化的情况
- new、设置或获取类的静态资源
- 反射调用
- 子类初始化,先初始化父类
- MethodHandle 和 VarHandle 可以看作是轻量级的反射调用机制
- Java8中,接口定义了默认方法时,接口实现类发生了初始化,先初始化接口
类卸载
- 卸载类是该类的Class对象被GC
- 满足的条件
- 该类的所有实例对象都已被GC,堆中不存在该类的实例对象
- 该类没有在其他任何地方被引用
- 该类的类加载器的实例已被GC:自定义加载器加载的类是可以被卸载掉的
类加载器
- JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自 java.lang.ClassLoader:
- BootstrapClassLoader( 启动类加载器) :最顶层的加载类,由C++实现,负责加载 %JAVA_HOME%/lib 目录下的 jar 包和类或者或被 -Xbootclasspath参数指定的路径中的所有类。
- ExtensionClassLoader (扩展类加载器) :主要负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包。
- AppClassLoader(应用程序类加载器) :面向我们用户的加载器,负责加载当前应用 classpath 下的所有jar包和类。
双亲委派模型
- 每一个类都有一个对应它的类加载器 。类加载的时候,会先判断这个类有没有被加载过。已经加载过的类直接返回,否则尝试加载。
- 加载的时候,会先委派该父类加载器的 loadClass 处理,因此所有请求都会到达顶层启动类加载器。当父类加载器无法处理时,才由自己来处理。
- 父类加载器为 null 时,由启动类加载器来处理。
双亲委派的好处
- 保证了 Java 程序的稳定运行,可以避免类的重复加载,也保证了 Java 的核心 API 不被篡改。
- 假设每个类加载器加载自己的话,就会出现我自定义一个 java.lang.Object 类的话,运行程序的时候,就有多个 Object 类了。
打破双亲委派
自定义加载器的话,需要继承 ClassLoader 。
- 如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的
findClass()
方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。 - 如果想打破双亲委派模型则需要重写
loadClass()
方法