掘金 后端 ( ) • 2024-06-26 14:24

类加载生命周期

类从被加载到Java虚拟机(JVM)内存开始,直至从内存中卸载,其完整的生命周期可分为七个阶段:

  1. 加载(Loading):查找并导入二进制字节流(.class文件),创建类的Class对象。

  2. 验证(Verification):确保被加载类的正确性,包括格式校验、语义校验、操作数栈和局部变量表的验证等。

  3. 准备(Preparation):为类的静态变量分配内存,并初始化为默认值(零值),但不执行任何实际的初始化赋值操作。

  4. 解析(Resolution):将符号引用转换为直接引用,如将类方法表中的方法引用解析为实际的方法地址。

  5. 初始化(Initialization):执行类构造器()方法,进行静态变量初始化和静态初始化块的执行。

  6. 使用(Using):类被JVM使用,执行其定义的方法和字段操作。

  7. 卸载(Unloading):当类不再被引用时,由垃圾收集器触发,卸载类的Class对象,释放其占用的内存资源。

其中,验证、准备和解析这三个阶段共同构成了连接(Linking)阶段。

image.png

在Java虚拟机(JVM)中,类加载器(ClassLoader)是负责加载类的组件,它负责从文件系统或网络中查找类的二进制数据,并转换成可在Java虚拟机内部使用的运行时数据结构(Class对象)。类加载器是Java平台实现动态类加载的关键组成部分,也是实现Java语言模块化和隔离的重要工具。

类加载器分类:

  1. 引导类加载器(Bootstrap ClassLoader)

    • 这是最顶层的类加载器,由C++编写,嵌入在JVM内核中,负责加载核心类库,如rt.jar等。
    • 由于引导类加载器是由C++实现的,所以在Java程序中通常显示为null,无法直接通过java.lang.ClassLoader的子类实例获取。
  2. 扩展类加载器(Extension ClassLoader)

    • 是由Java实现的,位于引导类加载器之下,负责加载<JAVA_HOME>/lib/ext目录下的类库,或者由java.ext.dirs系统变量指定的目录下的类库。
  3. 系统类加载器(Application ClassLoader)

    • 也称作应用程序类加载器,是Java程序默认的类加载器,负责加载classpath环境变量所指定的类库,通常是我们应用程序中的类加载入口。
  4. 自定义类加载器(Custom ClassLoader)

    • 开发者可以基于java.lang.ClassLoader类自定义类加载器,用于实现特定的类加载逻辑,例如从网络、数据库或者其他非传统途径加载类。

image.png

双亲委派模型:

  • 类加载器在加载类时,遵循“双亲委派模型”。当一个类加载器收到类加载请求时,它首先将请求委派给父类加载器去加载,直到到达引导类加载器。只有当父加载器无法完成类加载请求(返回null)时,当前加载器才会尝试自己去加载类。
  • 这种模型确保了类加载的有序性、稳定性以及安全性,特别是确保了Java基础类库的统一加载来源。

例如,当我们创建一个自定义类MyClass并继承自java.lang.Object时,类加载器会首先由系统类加载器尝试加载MyClass,然后沿着双亲委派链向上,如果需要的话,会加载Object类。即使MyClass在自定义类加载器中,基础类Object也会由引导类加载器加载。

双亲委派机制(父类委托机制):

在双亲委派模型下,当一个类加载器收到类加载请求时,它首先会将请求转发给其父类加载器。这一过程递归向上,直到请求传递到启动类加载器。只有当父加载器无法完成类加载请求(即在其搜索范围内找不到所需类)时,当前加载器才会尝试自行加载。这个过程确保了Java核心类库的唯一性和安全性。

具体实现在java.lang.ClassLoaderloadClass()方法中,加载过程首先检查类是否已加载,未加载则调用父加载器的loadClass()方法,若父加载器为空,则默认使用启动类加载器作为父加载器。如果父加载器加载失败,抛出ClassNotFoundException异常后,子类加载器再调用自己的findClass()方法尝试加载类。例如:

public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 若父加载器为空,则默认使用启动类加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 父加载器加载失败,尝试自己加载
                }
                if (c == null) {
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 自定义的类加载逻辑...
    }
}

image.png
例如,如果我们定义一个类Class A extends HttpServlet,在加载A类时,类加载器会按照双亲委派模型,先尝试由其父加载器加载HttpServlet及其依赖类,只有当父加载器无法完成加载时,才会由当前加载器尝试加载。

原文链接 https://www.hanyuanhun.cn | https://node.hanyuanhun.cn