1. 双亲委派模型

本文围绕Java类加载器展开,介绍了启动、扩展、应用程序3种类加载器。阐述双亲委派模型,即类加载请求先委派给父类加载器。通过源码解释该模型,还说明了自定义类加载器、使用该模型的原因、打破方式,以及线程上下文类加载器和类加载器组合关系。

1. Java类加载器类型

Java当中类加载器分为3种:

  1. 启动类加载器:在Hotspot VM中由C++实现
  2. 扩展类加载器:由Java实现,继承自ClassLoader
  3. 应用程序类加载器:由Java实现,继承自ClassLoader

代码打印下3种类加载器的类加载路径:

public class A {
    public static void main(String[] args) {
        System.out.println("Bootstrap ClassLoader加载范围:");
        URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
        Stream.of(urLs).forEach(System.out::println);
        System.out.println("==============================");


        URLClassLoader extClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader().getParent();
        System.out.println(extClassLoader);
        System.out.println("Extension ClassLoader加载范围:");
        urLs = extClassLoader.getURLs();
        Stream.of(urLs).forEach(System.out::println);
        System.out.println("==============================");

        URLClassLoader appClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        System.out.println(appClassLoader);
        System.out.println("Application ClassLoader加载范围:");
        urLs = appClassLoader.getURLs();
        Stream.of(urLs).forEach(System.out::println);
    }
}

运行结果:
类加载器加载范围

2. 双亲委派模型

双亲委派模型
双亲委派模型是:当一个类加载器收到类加载请求时,首先它不会自己去尝试加载这个类,而是把这个类加载请求委派给它的父类加载器去加载,每个加载器都是如此,一直到启动类加载器;当父类加载器在自己的搜索范围内找不到指定要加载的类时(ClassNotFoundException),子加载器才尝试自己去加载。

注意,类加载器的这种父子关系不是通过继承关系实现的,而是通过组合关系实现的,这种组合关系是通过ClassLoader类中的parent属性体现的,具体见8.类加载器组合关系体现

public abstract class ClassLoader {
	...
    private final ClassLoader parent;
    ...
}

3. 源码解释双亲委派模型

首先给出牵涉到的方法的流程图:
loadClass流程图

loadClass:
loadClass
findClass:

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

defineClass:

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError{
    return defineClass(name, b, off, len, null);
}

一般在findClass中调用defineClass,defineClass是JDK提供的将字节数组转化成Class对象。findClass找到要加载的类的.class文件,并读取成字节数组,然后defineClass将字节数组转换成Class对象。

4. 自定义类加载器

根据3中对双亲委派模型源码的分析,发现自定义类加载器只需要覆写findClass即可。

package mustang.horse;

public class A {
    public void hello() {
        System.out.println("hello");
    }
}

将A.java编译生成的class文件放在D盘中,然后删除项目中的A.java、A.class,防止因为双亲委派由AppClassLoader加载A类。

public class Main {
    static class MyClassLoader extends ClassLoader {
        private String classPath;

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

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

        private byte[] loadBytes(String name) throws IOException {
            name = name.replaceAll("\\.", "/");
            FileInputStream fileInputStream = new FileInputStream(classPath + "/" + name + ".class");
            int length = fileInputStream.available();
            byte[] bytes = new byte[length];
            fileInputStream.read(bytes);
            fileInputStream.close();
            return bytes;
        }
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader("D:");
        Class<?> clazz = myClassLoader.loadClass("mustang.horse.A");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("hello", null);
        method.invoke(obj, null);
        System.out.println(obj.getClass().getClassLoader());
    }

}

输出结果:
自定义类加载器

5. 为什么使用双亲委派模型?

使用双亲委派的原因:

  1. 避免用户编写的类动态替换Java的核心类。比如用户编写了一个类java.lang.Object,要加载时,按照双亲委派模型,最终启动类加载器会在自己的类加载路径下搜索到java.lang.Object,而不是用户定义的Object,避免了java核心类被用户替换。(其实,自定义类加载器打破双亲委派模型后,也不能加载自己写的Object类替换java核心类库中的Object,因为JVM对java.*开头的类做了限制,保证必须由Bootstrap类加载器加载,具体见下图)
  2. 避免了类的重复加载。相同的类被不同的ClassLoader加载就是不同的两个类,按照双亲委派模型,指定类只会被一个类加载器加载,不会重复加载。
    JVM限制java.*类

6. 打破双亲委派模型

根据对双亲委派模型源码的分析,打破双亲委派模型,只需要在自定义类加载器时重写loadClass即可。

public class Main {
    static class MyClassLoader extends ClassLoader {
        private String classPath;

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

        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            try {
                byte[] bytes = loadBytes(name);
                return defineClass(name, bytes, 0, bytes.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

        private byte[] loadBytes(String name) throws IOException {
            name = name.replaceAll("\\.", "/");
            FileInputStream fileInputStream = new FileInputStream(classPath + "/" + name + ".class");
            int length = fileInputStream.available();
            byte[] bytes = new byte[length];
            fileInputStream.read(bytes);
            fileInputStream.close();
            return bytes;
        }
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader("D:");
        Class<?> clazz = myClassLoader.loadClass("mustang.horse.A");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("hello", null);
        method.invoke(obj, null);
        System.out.println(obj.getClass().getClassLoader());
    }

}

运行结果:
找不到Object
发现打破双亲委派模型后,Main的父类Object也需要自定义的累加器加载,但是在该自定义累加器的加载路径中并没有Object这个类。

7. 线程上下文类加载器

类A依赖类B,在类A加载的时候,如果类B没有加载,那么类A会加载自己的类加载器去加载类B。
如果类A需要使用Bootstrap类加载器加载(存在于java核心包中的类),类B需要使用App类加载器加载,类A依赖类B,那怎么办呢?
JDK1.2 开始引入线程上下文类加载器:
在上面的情景中,类A加载时就可以拿到当前线程上下文类加载器去加载类B。(如果这个线程上下文类加载器恰好是App类加载器的话)

Thread.currentThread().getContextClassLoader();

新建线程如果不设置上下文类加载器的话,默认会继承父线程上下文类加载器。默认情况下,线程上下文类加载器就是App ClassLoader。也可以通过下面方法设置。

Thread.currentThread().setContextClassLoader(ClassLoader cl);

线程上下文类加载器也是打破双亲委派模型的一种方式,因为在双亲委派模型中父类加载器是无法委派给子类加载器的。

8. 类加载器组合关系体现

首先看ExtClassLoader、AppClassLoader类关系图:
ExtClassLoader、AppClassLoader类关系图
ExtClassLoader构造函数:ExtClassLoader在构造时,其parent属性指定为null。
ExtClassLoader构造函数

AppClassLoader构造函数:AppClassLoader在构造时,其parent属性会通过构造函数传入。
AppClassLoader构造函数
再来看Launcher的构造方法:

Launcher的构造方法
发现,先构造出ExtClassLoader,再构造AppClassLoader,并且将ExtClassLoader设置为AppClassLoader的父类加载器。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值