Java ClassLoader与双亲委派模型

本文深入探讨了Java中的ClassLoader机制,包括启动类加载器、扩展类加载器和应用程序类加载器,以及双亲委派模型的工作原理和好处。双亲委派模型确保了类的安全加载,防止恶意代码替换核心类。同时,文章还介绍了如何自定义ClassLoader,通过示例展示了自定义加载器加载类的过程。

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

概述

这里要先说明下java中的ClassLoader 和Android中的ClassLoader是有区别的。
Android从5.0开始就采用art虚拟机, 该虚拟机有些类似Java虚拟机, 程序运行过程也需要通过ClassLoader 将目标类加载到内存中。本篇文章先带大家梳理下java的ClassLoader。

java中的ClassLoader

类加载就是多种类加载器(ClassLoader)来查找和加载Class文件到Java虚拟机中。类加载器就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。

ClassLoader的类型

java中的类加载器主要有两种类型,即系统类加载器和自定义类加载器。其中系统加载器包括3种。

  • 启动类加载器(Bootstrap
    ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。
  • 其他类加载器:由Java语言实现,继承自抽象类ClassLoader。如:
    • 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。
    • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

双亲委派模型

什么是双亲委派模型?

类加载器查找Class所采用的是双亲委派模式,如果一个类加载器收到类加载的请求,首先判断该类是否已经加载,如果没有不是自身去查找而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。

在这里插入图片描述

为什么需要双亲委派模型,有什么好处?

双亲委派模型有两个好处

  • 避免重复加载,如果已经加载过一次Class,就不需要再次加载,而是直接读取已经加载的Class。

  • 更加安全。

如果不使用双亲委派模型,就可以自定义一个 java.lang.String类,这个类和系统的String类一样的功能,如果在这某个函数执行一些“恶意代码”。并且通过自定义类加载器加入到JVM中。此时,如果没有双亲委派模型,那么JVM就可能误以为自定义的java.lang.String类是系统的String类,导致“恶意代码”被执行。

而有了双亲委派模型,自定义的java.lang.String类永远不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String类,即系统的String类在java虚拟机启动时就被加载。或许你会想,我在自定义的类加载器里面强制加载自定义的java.lang.String类,不去通过调用父加载器不就好了吗。确实这样是可以操作的。但是在JVM中,判断一个对象是否是某个类型时,如果该对象的实际类型和比较的类型的类加载器不同,那么返回false。

所以java虚拟机判断两个对象是否是同一个类,需要两个类名一致并且被同一个类加载器加载。

如何实现双亲委派模型?

每次先通过父类加载器加载,当父类加载器无法加载时,再自己加载。

ClassLoader :: loadClass

   protected Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
        synchronized(this.getClassLoadingLock(var1)) {
            Class var4 = this.findLoadedClass(var1); //首先检查传入的类是否已经加载,如果已经加载则不执行后续代码
            if (var4 == null) {
                long var5 = System.nanoTime();

                try {
                    if (this.parent != null) {
                        var4 = this.parent.loadClass(var1, false); //如果父类加载器不为null,则调用父类加载器的 loadClass
                    } else {
                        var4 = this.findBootstrapClassOrNull(var1); //如果没有父类加载器,则执行自身 findBootstrapClassOrNull
                    }
                } catch (ClassNotFoundException var10) {
                }

                if (var4 == null) {       //继续向下进行查找流程
                    long var7 = System.nanoTime();
                    var4 = this.findClass(var1);
                    PerfCounter.getParentDelegationTime().addTime(var7 - var5);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(var7);
                    PerfCounter.getFindClasses().increment();
                }
            }

            if (var2) {
                this.resolveClass(var4);
            }

            return var4;
        }
    }

让我们最后小结一下 loadClass (String,boolean) 函数实现的双亲委派模型!

  1. 首先,检查一下置顶名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回
  2. 如果此类没有加载过,那么再判断一下是否有父加载器;如果有父加载器,则有父加载器(即调用parent.loadClass(var1, false));或者调用 Bootstrap类加载器来加载
  3. 如果父加载器以及Bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的 findClass方法来完成类加载。

自定义ClassLoader

首先看下默认的classLoader

package main.java;

public class Test1 {
    public void hello() {
        System.out.println("当前类由" + getClass().getClassLoader().getClass()
                + " 加载");
    }
}

输出

当前类由class sun.misc.Launcher$AppClassLoader 加载

注意
这里我们需要先将Test1.java 用 javac 编译成 Test1.class,再将Test.java删除。因为如果Test.java存放在当前目录中,根据双亲委派模型可知,会通过 sun.misc.Launcher$AppClassLoader类加载器加载。

接下来我们自定义classLoader

package main.java;

import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;

public class CustomClassLoader extends ClassLoader {

    private String classPath;

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

    private byte[] loadByte(String name) throws Exception {
        name = name.replaceAll("\\.", "/");
        FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    public static void main(String args[]) throws Exception {
        File directory = new File("");//设定为当前文件夹
        System.out.println(System.getProperty("java.version"));
        try {
            System.out.println(directory.getAbsolutePath());//获取绝对路径
        } catch (Exception e) {
        }
        System.out.println("---->"+directory.getAbsolutePath()+"/src"); 
        //要用自定义的classloader 需要将原先的Test1.java删除
        CustomClassLoader customClassLoader = new CustomClassLoader(directory.getAbsolutePath()+"/src");//这里就是你当前存放class的路径
        Class clazz = customClassLoader.loadClass("main.java.Test1");//这里就是你class文件的编译前的包名+类名
        Object obj = clazz.newInstance();
        Method helloMethod = clazz.getDeclaredMethod("hello", null);
        helloMethod.invoke(obj, null);

    }
}

输出

当前类由class main.java.CustomClassLoader 加载
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值