JVM类加载机
类的生命周期
加载
类的加载由类加载器完成,加载是指将类的字节码文件中的二进制数据读入到内存中,放在运行时数据区的方法区内,然后在堆创建java.lang.Class对象,来封装类在方法区内的数据结构。
链接
链接分为三个阶段:
- 验证:确保加载的类信息符合JVM规范,没有安全问题。
- 准备:为类的静态变量分配内存,设置初始值,赋值在初始化阶段完成。final修饰的静态变量值在编译阶段已经确定,赋值在准备阶段完成。
- 解析:JVM将常量池内的符号引用替换为直接引用。
- 符号引用
- 直接引用:指针指向目标,确定在内存中的地址。
初始化
初始化阶段负责静态代码块的执行以及静态变量的赋值。 先初始化父类,再初始化当前类。 只有主动使用类时才会初始化。
类初始化的时机:
- 创建类的实例(new、反射、反序列化)
- 首次调用某个类的静态方法/使用某个类的静态变量
- 初始化某个类的子类时,其所有父类都会被初始化
使用反射来强制创建某个类或接口对应的java.lang.Class对象,不会初始化。
使用
类完成初始化后,进入可用状态。
卸载
同时满足以下条件,JVM会回收类的元数据及对应的class对象:
- 类的所有实例都被垃圾回收
- 类的class对象没有任何引用
- 加载该类的classLoader实例被回收
类加载器
-
BootStrap ClassLoader 启动类加载器
负责加载
JAVA_HOME/jre/lib目录下的jar包。 -
Extension ClassLoader 扩展类加载器
负责加载jre扩展目录
%JAVA_HOME/jre/lib/ext中jar包的类。 -
Application ClassLoader 应用类加载器
负责加载当前应用classpath下所有的jar包和类。
双亲委派机制
自底向上检查类是否被加载,自顶向下尝试加载类
- 在类加载的时候,加载器将加载请求自底向上委派给自己父类,判断当前类是否被加载过,已经加载过的类直接返回,否则尝试加载。
- 加载器加载类时,顶层启动类加载器自顶向下尝试加载类,如果能加载就返回,不能加载才让子类加载器尝试加载。
优点:
- 避免类重复加载
- 避免Java核心API被篡改,保证安全
打破双亲委派机制
如何打破双亲委派机制
实现双亲委派机制的的代码都在java.lang.ClassLoader的loadClass方法中。 因此,只需要自定义类加载器,重写loadClass方法,保证内部不遵循双亲委派,就可以打破双亲委派机制。
- 默认loadClass逻辑:检查类是否已经加载过,没有加载就调用父类的loadClass方法,父类加载器为空默认使用启动类加载器作为父类加载器。父类加载失败,抛出ClassNotFoundException,再调用自己的findClass方法进行加载。
e.g. Tomcat破坏双亲委派
一个Tomcat可以运行多个Web应用程序,不同的应用可能同时依赖相同的类库 但是使用版本不同,但是这些类库中Class的全路径名是相同的,使用双亲委派不能重复加载同一个类。
- 每个应用都使用一个独立的Tomcat自定义类加载器WebappClassLoader进行加载,针对私有类不遵循双亲委派,自己加载,即使全类名相同,由于类加载器不同,JVM也会认为是不同类。
- SharedClassLoader加载类,各个web应用共享使用,避免重复加载。