• Java高级——类加载器和双亲委派模型


    简述

    类通过类加载器加载,一个类由加载它的类加载器和其本身决定其在JVM中的唯一性,每一个类加载器,都拥有一个独立的类名称空间

    public class Test {
    
        public static void main(String[] args) throws Exception{
            ClassLoader myLoader = new ClassLoader() {
                @Override
                public Class loadClass(String name) throws ClassNotFoundException {
                    try {
                        String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                        InputStream is = getClass().getResourceAsStream(fileName);
                        if (is == null) {
                            return super.loadClass(name);
                        }
                        byte[] b = new byte[is.available()];
                        is.read(b);
                        return defineClass(name, b, 0, b.length);
                    } catch (IOException e) {
                        e.printStackTrace();
                        throw new ClassNotFoundException(name);
                    }
                }
            };
            Object obj = myLoader.loadClass("Test").newInstance();
            System.out.println(obj.getClass());
            System.out.println(obj instanceof Test);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    如上,自定义了一个类加载器去加载Test,instanceof 输出false

    class Test
    false
    
    • 1
    • 2

    类加载器

    从JVM角度看存在

    • 启动类加载器(Bootstrap ClassLoader):由C++实现,是JVM的一部分,java中用null代替
    • 其他类加载器:由Java实现,独立于JVM,继承自ClassLoader

    从开发者角度看存在:

    • 启动类加载器(Bootstrap Class Loader):加载\lib或-Xbootclasspath指定路径中的类库
    • 扩展类加载器(Extension Class Loader):在sun.misc.Launcher$ExtClassLoader,加载\lib\ext或java.ext.dirs指定路径中的类库
    • 应用程序类加载器(Application Class Loader):在sun.misc.Launcher$AppClassLoader,通过ClassLoader.getSystemClassLoader()返回,加载用户类路径(ClassPath)上的类库

    在这里插入图片描述

    除了启动类加载器外,其余的类加载器都有自己的父类加载器,通过组合而非继承关系来复用父加载器的代码

    双亲委派模型

    • 任意类加载器收到了类加载的请求
    • 把请求委派给父类加载器去完成(直到传给启动类加载器)
    • 当父类加载器无法完成加载(其搜索范围未找到所需的类)
    • 子加载器才会尝试去加载

    双亲委派模型让类随着它的类加载器形成优先级的层次关系,如对于Object,无论哪个类加载器加载,最终都委派给启动类加载器,从而保证了唯一性

    protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // 首先,检查请求的类是否已经被加载过了
        Class c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 如果父类加载器抛出ClassNotFoundException
                // 说明父类加载器无法完成加载请求
            }
            if (c == null) {
                // 在父类加载器无法加载时
                // 再调用本身的findClass方法来进行类加载
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    上面为java.lang.ClassLoader的loadClass()方法中双亲委派模型的代码实现

    破坏双亲委派模型

    情况一

    双亲委派模型在JDK1.2推出,在此之前的类加载器都是通过复写loadClass()方法加载类

    为了兼容,无法再以技术手段避免loadClass()被子类复写,即可以通过复写loadClass()方法双亲委派模型

    为了避免破坏双亲委派模型,JDK1.2新增protect的findClass()方法,让用户尽可能重写findClass()而不是loadClass(),这样既不影响用户自定义加载类,又可保证新写的类加载器符合双亲委派模型

    情况二

    根据双亲委派模型,越基础的类由越上层的加载器加载,基础类型是作为被用户代码继承、调用的API存在

    而基础类型要调用回用户的代码(加载资源类),则需通过线程上下文类加载器(Thread Context ClassLoader)

    • 其通过Thread的setContextClassLoader()方法设置
    • 若创建线程时还未设置,将会从父线程中继承一个
    • 若在应用程序的全局范围内都未设置,则默认使用Application Class Loader

    这是一种由父类加载器去请求子类加载器完成类加载的行为,打通了双亲委派模型的层次结构来逆向使用类加载器,典型应用有JNDI、JDBC等

    情况三

    IBM推出的OSGi为了实现模块热部署,每个程序模块(称为Bundle)都有自身的类加载器,当需要更换Bundle时连同类加载器一起换掉以实现代码的热替换

    在OSGi环境下,类加载器不再是双亲委派模型的树状结构,而是网状结构,当收到类加载请求时,按照下面的顺序进行类搜索

    • 将以java.*开头的类,委派给父类加载器加载
    • 否则,将委派列表名单内的类,委派给父类加载器加载
    • 否则,将Import列表中的类,委派给Export这个类的Bundle的类加载器加载
    • 否则,查找当前Bundle的ClassPath,使用自己的类加载器加载
    • 否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载
    • 否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载
    • 否则,类查找失败

    上面只有前两点仍符合双亲委派模型,其余的类查找都是在平级的类加载器中进行的

  • 相关阅读:
    赛轮集团SAILUN方程式赛车轮胎震撼登场,开启新篇章
    web综合案例-day01
    4个视频教你正确使用华为云代码托管服务CodeArts Repo!
    7 IT Career Paths and How to Get Started in 2023
    计算机网络-性能指标
    磁场设备—螺线管
    java Spring框架 XML配置和注解配置的区别
    无法启动此程序win10玩游戏找不到d3dx9_43.dll缺失的五种常用有效解决方法
    【英语考研词汇训练营】Day 15 —— analyst,general,avoid,surveillance,compared
    个人信息安全工程指南
  • 原文地址:https://blog.csdn.net/qq_35258036/article/details/127634924