类加载——双亲委派机制

128 阅读3分钟

先说说什么是类加载器

  • 类加载器是Java虚拟机提供给应用程序去实现和获取类和接口字节码的技术,用于动态加载Java类到内存中(将字节码转换为byte[ ])的组件

四种类加载器

  1. 启动类加载器(Bootstrap ClassLoader),加载核心类,比如String类
  2. 扩展类加载器(Extension ClassLoader),加载扩展类
  3. 应用程序类加载器(Application ClassLoader),加载classpath中的类
  4. 自定义类加载器,一般重写findClass方法

注: JDK9之后扩展类加载器 (Extension ClassLoader) 变为平台类加载器 (Platform ClassLoader)


什么是双亲委派机制

  • 每个Java实现的类加载器中都有一个属性,可以理解为存放的是其父类加载器

    在这里插入图片描述

启动类加载器是用本地代码,通常是C/C++写的,所以用java代码不能获取,结果返回为null,所以扩展类的parent属性是null,但是逻辑上来说启动类加载器还是相当于它的父类 JDK9引入了module的概念,类加载器在设计上发生很大改变,启动类加载器使用java编写,不过仍然无法通过java代码获取到,返回依然是null

  • 自底向上进行查找: 如果类没有被当前加载器加载过,则查找父类有没有加载过,如果加载过则返回,如果没有则继续向上查找
  • 由顶向下进行加载: 如果直到启动类加载器都没有被加载过,则由父类尝试加载,再由子类尝试,直到被加载并返回

双亲委派机制的好处

  1. 防止类的重复加载:通过双亲委派机制,每个类加载器在加载类时都会委派给其父加载器,因此可以避免同一个类被多个加载器加载,保证了类的唯一性
  2. 保护类的安全性:双亲委派机制可以防止恶意类的加载,即使是恶意类也无法绕过双亲委派机制直接被加载,它们必须被放置在被信任的类路径下才能被加载
  3. 保护核心类库的完整性:通过双亲委派机制,Java核心类库(如java.lang包)被放置在引导类加载器的类路径下,这样可以保护核心类库的完整性,防止被篡改或替换,确保了Java运行环境的稳定性和安全性

源码简单分析

  • ClassLoader中有几个重要方法

    1.类加载的入口,提供双亲委派机制,内部调用findClass
    loadClass(String):Class<?>

    2.类加载器子类实现,获取二进制数据调用defineClass findClass(String):Class<?>

    3.做类名校验,调用虚拟机底层方法将字节码加载到内存 defineClass(String, byte[], int, int):Class<?>

    4.执行类生命周期连接阶段 resolveClass(Class<?>):void

  • ClassLoader中的loadClass方法

//1.String name 被加载类名
protected Class<?> loadClass(String name, boolean resolve)  
    throws ClassNotFoundException  
{  
    //2,加锁防止在多线程条件下重复加载类
    synchronized (getClassLoadingLock(name)) 
    {  
        //3,查找此类是否被加载过,若被加载则返回Class对象
        Class<?> c = findLoadedClass(name);  
        //4,如果c为空,则说明未被加载过,否则,是被加载过,跳到10
        if (c == null) {  
            long t0 = System.nanoTime();  
            try {  
                //5,判断父类加载器是否为null
                if (parent != null) {
                    //6,如果不为空,则调用父类loadClass方法  
                    c = parent.loadClass(name, false);  
                } 
                else {  
                    //7,如果为空,说明父类为启动类加载器,调用本地方法来加载
                    c = findBootstrapClassOrNull(name);  
                }  
            } 
            catch (ClassNotFoundException e) {  
            }  

            //8,如果c为空,说明其父类都没有加载成功,则由当前类加载器来进行加载
            if (c == null) {
            
            //9,加载类  
                c = findClass(name); 
                 
                PerfCounter.getParentDelegationTime().addTime(t1 - t0);  
                PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);  
                PerfCounter.getFindClasses().increment();  
            }  
        }  
        
        if (resolve) {  
            resolveClass(c);  
        }  
        
        //10,返回Class对象
        return c;  
    }  
}

打破双亲委派机制的方式

  1. 自定义类加载器,重写loadClass方法,不再实现双亲委派机制
  2. JNDI,JDBC,JCE,JAXB 和 JBI 等框架使用了SPI机制+线程上下文类加载器
  3. OSGI 允许同级类加载器相互调用