JVM类加载器和双亲委派机制详解

目录

1、JVM类加载器

2、双亲委派模型

3、自定义类加载器的实现


1、JVM类加载器

        JVM的类加载器(Class Loader)是Java虚拟机的一个重要组成部分,负责将Java类文件加载到JVM内存中并转换成Java类模板(Class对象),供JVM执行字节码时使用。

        Java虚拟机规范中定义了三种类加载器:

  1. 引导类加载器(Bootstrap Class Loader):负责加载Java虚拟机核心库(rt.jar等),以及其他类加载器的实现类等,通常使用C++实现,是JVM自身的一部分,不是Java类。

  2. 扩展类加载器(Extension Class Loader):负责加载Java虚拟机扩展库(ext目录下的jar文件),通常由Java语言实现,是一种系统级的类加载器。

  3. 应用程序类加载器(Application Class Loader):负责加载应用程序的类文件(classpath中指定的jar或class文件),也称为系统类加载器(System Class Loader),通常由Java语言实现。

        除了这三种标准的类加载器之外,Java还提供了自定义的类加载器(Custom Class Loader),允许开发者自行实现特定的类加载器,以满足一些特殊的需求,比如动态加载类、保护代码的安全性等。

        JVM的类加载器采用了委托机制,即先委托父类加载器加载类,如果父类加载器无法加载,则由子类加载器加载,这样可以保证类的唯一性和一致性,避免类的重复加载和冲突。这种机制也被称为双亲委派模型(Parent Delegation Model)。

        类加载器的作用不仅限于加载类文件,还包括类的链接和初始化等,其中链接包括验证、准备和解析三个阶段,而初始化则是类加载的最后一个阶段,负责执行静态代码块和变量的初始化操作。

        使用Java程序获取代码中的类加载器:

public class ClassLoaderTest {
    public static void main(String[] args) {
        //Application ClassLoader
        System.out.println(ClassLoader.getSystemClassLoader());
        //Extensions ClassLoader
        System.out.println(ClassLoader.getSystemClassLoader().getParent());
        //Bootstrap ClassLoader
        System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
    }
}

        上述代码的输出结果为:

JVM类加载器和双亲委派机制详解_第1张图片

        通过这段代码可以看出,我们日常编写的代码,是通过Application ClassLoader加载的,其父类是Extensions ClassLoader。实际上,Bootstrap ClassLoader也是Extensions ClassLoader的父类,但是因为Bootstrap ClassLoader是由C++编写的,在Java中无法获取,所以对外显示为null。

2、双亲委派模型

        双亲委派模型是指在 Java 类加载器的工作机制中,一个类加载器在加载某个类时,会优先委派给父类加载器去完成,直到最终委派给顶层的启动类加载器。只有当父类加载器无法完成加载任务时,才由自己的类加载器去加载。

        这种机制的优点在于可以避免重复加载已经存在的类,从而保证了程序的稳定性和安全性。例如,如果某个应用程序已经加载了 JDK 提供的核心类库,那么同样的类库在其他第三方库中不应该再次被加载,而应该直接使用已经加载的类库。

        具体来说,当一个类加载器需要加载某个类时,它会首先向自己的父类加载器发送加载请求,如果父类加载器能够找到并加载该类,那么就直接返回该类的 Class 对象。如果父类加载器无法加载该类,那么该类加载器才会尝试自己去加载该类。如果该类加载器还有自己的子类加载器,它还会向子类加载器发送加载请求。如果所有的父类和子类加载器都无法加载该类,那么该类加载器才会尝试自己去加载该类。

        双亲委派机制下的类加载过程图示:

JVM类加载器和双亲委派机制详解_第2张图片

        这种双亲委派模型可以保证 Java 类的全局唯一性,避免了类的重复加载,同时也使得 Java 类的加载更加安全和可靠

双亲委派机制的好处(总结):

(1)首先,保证了java核心库的安全性。如果你也写了一个java.lang.String类,那么JVM只会按照上面的顺序加载jdk自带的String类,而不是你写的String类。

(2)其次,还能保证同一个类不会被加载多次

3、自定义类加载器的实现

        Java 中的类加载器是由 JVM 运行时动态加载的,程序员通常无需直接操作类加载器。不过在一些特殊的情况下,我们可能需要使用自定义的类加载器来实现一些特殊的需求,比如实现热部署功能,从非标准的文件系统、数据库、网络等地方加载类文件。

        自定义 JVM 类加载器的一般步骤如下:

  1. 定义自定义类加载器,一般需要继承 java.lang.ClassLoader 类,并实现 findClass() 方法来加载指定的类。

  2. 在程序中通过自定义类加载器来加载指定的类。可以使用 loadClass() 方法来加载类,该方法会调用 findClass() 方法来实现类的加载。

  3. 如果要加载的类已经被父类加载器加载过了,那么可以使用 getParent() 方法来获取其父类加载器,然后调用父类加载器的 loadClass() 方法来完成类的加载。

        以下是一个简单的使用自定义类加载器的 Java 代码示例:

public class CustomClassLoaderTest {
    public static void main(String[] args) throws Exception {
        // 创建自定义类加载器实例
        CustomClassLoader customClassLoader = new CustomClassLoader("D:/my_classes");
        // 加载类
        Class clazz = customClassLoader.loadClass("com.example.MyClass");
        // 创建对象
        Object obj = clazz.newInstance();
        // 调用对象的方法
        Method method = clazz.getMethod("sayHello");
        method.invoke(obj);
    }
}

class CustomClassLoader extends ClassLoader {
    private String classesPath; // 类文件所在路径

    public CustomClassLoader(String classesPath) {
        this.classesPath = classesPath;
    }

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        try {
            // 读取类文件字节码
            byte[] data = loadClassData(name);
            // 使用 defineClass 方法将字节码转换成 Class 对象
            return defineClass(name, data, 0, data.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name, e);
        }
    }

    private byte[] loadClassData(String className) throws IOException {
        // 读取类文件
        File classFile = new File(classesPath + "/" + className.replace('.', '/') + ".class");
        try (InputStream is = new FileInputStream(classFile)) {
            // 将类文件转换成字节数组
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            int nextValue = is.read();
            while (nextValue != -1) {
                byteStream.write(nextValue);
                nextValue = is.read();
            }
            return byteStream.toByteArray();
        }
    }
}

        这段代码中定义了一个名为 CustomClassLoader 的自定义类加载器,并通过 findClass 方法实现了从指定路径加载类文件的功能。在 CustomClassLoaderTest 类的 main 方法中,创建 CustomClassLoader 实例,使用该实例加载指定的类文件,并创建该类的实例并调用其方法。

        自定义类加载器也需要遵循双亲委派模型,即在加载一个类之前,先向其父类加载器请求加载。如果父类加载器无法加载该类,再由自定义类加载器来尝试加载。

        自定义类加载器的使用非常灵活,我们可以在其中实现各种各样的功能,比如动态加载类、加密解密类等等。但是,由于类加载器的复杂性,我们在实际开发中应该尽量避免使用自定义类加载器,除非必要。

你可能感兴趣的:(Java,Virtual,Machine,(JVM),JVM类加载器,双亲委派模型,自定义类加载器)