Java虚拟机:类加载器与双亲委派模型

本文详细介绍了Java虚拟机中的类加载器类型及其工作原理,包括启动类加载器、扩展类加载器和应用类加载器。此外,还探讨了类加载过程中的双亲委派模型,并通过示例代码展示了如何实现自定义类加载器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学了挺久的java,也接触过java虚拟机,但是就是没有深入到源码中细细品读,也导致每次看到关于虚拟机的问题的时候都似曾相识却无从下手.这个系列就一点一点的品读java虚拟机.

类加载器

从java虚拟机规范描述来看,JVM支持两种类加载器:引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader).自定义类加载器派生于抽象类ClassLoader.

最常见的类加载器始终只有3个:
* Bootstrap ClassLoader 启动类加载器(引导加载器)
* ExtClassLoader
* AppClassLoader

对应来看,每种类加载器对应的目录为:

类加载器目录备注
Bootstrap ClassLoaderJAVA_HOME/lib,或者由选项-Xbootclasspath指定用C++编写,嵌在JVM内部
ExtClassLoaderJAVA_HOME/extJava编写 派生于ClassLoader
APPClassLoaderClassPath

测试代码:

public class Test {
    public static void main(String[] args) {
        //BootstrapClassLoader
        ClassLoader classLoader = System.class.getClassLoader();
        System.out.println(null != classLoader ? classLoader.getClass().getName(): "name:null");
        //ExtClassLoader
        System.out.println(CollationData_ar.class.getClassLoader().getClass().getName());
        //AppClassLoader
        System.out.println(Test.class.getClassLoader().getClass().getName());
    }

}

输出结果:

name:null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader

以上测试的含义是:
1. 因为java.lang.System类包含在JAVA_HOME/lib目录中,测试由BootstrapClassLoader加载,由于启动类加载器本身由C++编写并嵌套在JVM内部,所以输出为null.
2. sun.text.resoutces.CollationData_ar类包含在JAVA_HOME/lib/ext扩展目录中,由ExtClassLoader加载.
3. 而我们的一般测试类在ClassPath目录中,因此由AppClassLoader加载.

双亲委派模型

java程序中重要的一点是要保证一个类的全局唯一性.当程序中出现多个全限定名相同的类时,类加载器始终只加载其中的某一个类而不会都加载.

查看ClassLoader类中loadClass的源码:

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        //1.检查目标类之前是否已经被加载过了
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //如果存在超类加载器,就直接委派给顶层的启动类加载器.
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    //如果超类加载器无法加载,就自行加载
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

双亲委派模型的优点是使java类加载器具备优先级层级关系,越是基础的类越是被上层的类加载器加载,保证稳定运行.

**注意:**java虚拟机规范没有明确要求类加载器机制一定是双亲委派模型,只是建议.比如在Tomcat中,默认类加载器首先自己加载,失败时才给超类加载.

自定义类加载器

自定义类加载器的需求并不多.但是,当有一些特殊需求,比如,当一个字节码文件在编译的时候进行了加密处理,那么类加载器在加载的时候首先就要解密,否则会认为它不是标准的字节码文件.

自定义类加载器只需继承抽象类ClassLoader并重写ClassLoader.

注意:怎样查看当前的ClassPath目录?

System.getProperty("java.class.path");

准备工作

在F盘目录下创建Test1.java文件:

public Test1{
        public static void main(String[] args){
                System.out.println("test1");
    }
}

并在此打开命令行,用javac Test1.java 命令将其编译为Test1.class 文件.

程序代码

public class MyClassLoader extends ClassLoader {
    private String byteCode_Path;

    public MyClassLoader(String byteCode_Path) {
        this.byteCode_Path = byteCode_Path;
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        byte value[] = null;
        BufferedInputStream in = null;
        try {
            in = new BufferedInputStream(new FileInputStream(byteCode_Path + className + ".class"));
            value = new byte[in.available()];//将字节码全部读取到数组里
            in.read(value);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != in) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return defineClass(value, 0, value.length);//将byte数组转化为一个class对象实例
    }

    public static void main(String[] args) throws ClassNotFoundException {
        MyClassLoader classLoader = new MyClassLoader("F:/");
        System.out.println("加载目标类的类加载器->"+classLoader.loadClass("Test1").getClassLoader().getClass().getName());
        System.out.println("当前类加载器的超类加载器:->"+classLoader.getParent().getClass().getName());

    }
}

输出为:

加载目标类的类加载器->MyClassLoader
当前类加载器的超类加载器:->sun.misc.Launcher$AppClassLoader

参考书目:《Java虚拟机精讲》 高翔龙

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值