美文网首页Android座谈会
JVM必备基础知识(二)-- 类加载器和双亲委派模型

JVM必备基础知识(二)-- 类加载器和双亲委派模型

作者: ClericYi | 来源:发表于2020-02-09 11:48 被阅读0次

    本章内容是对《深入理解Java虚拟机:JVM高级特性和最佳实践》的理解和概括。

    前言

    在上文中我们已经讲了类的加载机制,这一章的主角就是类加载器和双亲委派模型了。

    类加载器

    在Java虚拟机中,类加载器十分重要。每一个类的加载,都需要通过一个类的加载器。但是如果我们创建一个属于自己的类加载器,这个时候会出现一个什么样的情况呢?
    接下来,我们用代码来进行验证测试。

    public class ClassLoaderTest {
        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) {
                        throw new ClassNotFoundException(name);
                    }
                }
            };
            // 由自己的类加载器创建对象
            Object obj = myLoader.loadClass("XuNiJi.ClassLoaderTest").newInstance();
            // 由系统提供的类加载器加载创建对象
            Object obj1 = ClassLoader.getSystemClassLoader().loadClass("XuNiJi.ClassLoaderTest").newInstance();
            System.out.println(obj instanceof ClassLoaderTest);
            System.out.println(obj1 instanceof ClassLoaderTest);
        }
    }
    

    从这里想来已经能够看出了,由一个类加载器统一创建的类,才存在可比性。因为类加载器是拥有独立的类名称空间的。更简单的说,就像上面的例子,如果不使用Java虚拟机提供的类加载器,你就会失去一大部分功能,比如equals()isAssignableFrom()isInstance()instanceof。如果要相同,除非你直接在java源码上动手脚。

    双亲委派模型

    第一个问题:为什么需要这个模型?
    其实这个模型的提出,就是为了解决类加载器可能不出现不同的问题。因为即便是相同的class,由不同的类加载器加载时,结果就是不同的。

    工作原理

    双亲委派的工作流程非常简单,这就跟之前文章里的Android的事件分发机制一样,向上传递,由上一层的加载器先行尝试消费,如果上一层无法完成这个任务,那么子加载器就要由自己动手完成。

    1. 启动类加载器:负责加载/lib下的类。
    2. 扩展类加载器:负责加载/lib/ext下的类。
    3. 系统类加载器/应用程序类加载器:ClassLoader.getSystemClassLoader返回的就是它。

    通过上图我们可以知道,子加载器不断的给上一层加载器传递加载请求,那么这个时候启动类加载器势必是接受到过全部的加载请求的。如果不信,我们就用源码来证明。

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // 判断Class是否被加载过
                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
                        // 说明父类无法完成加载
                    }
                    // 这个时候c依旧为null,说明父类加载不了
                    // 那没有办法,只能子加载器自己效劳了
                    if (c == null) {
                        long t1 = System.nanoTime();
                        c = findClass(name);
    
                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
    }
    

    讲完了他的工作原理,自然就要知道,他能够如何被破坏的了。

    破坏双亲委派模型

    第二个问题,为什么要破坏双亲委派?
    拿最简单的例子,在上文中我们,提到过各个资源的加载范围,但是Driver作为后来才加入的一个接口,他的很多api是由第三方服务商开发的。那么这个时候,破坏双亲委派就有了他的用武之地了,当然这只是他的用处之一。

    下面来介绍,他是如何破坏双亲委派的。
    先看看我们平时都是怎么用的。(当然这是很基础的写法了,因为现在池的概念加深,所以很多事情都已经被封装了。)

    String url = "jdbc:mysql://localhost:3306/db";
    Connection conn = DriverManager.getConnection(url, "root", "root"); 
    

    上面很明显就能看出这件事情就是关于DriverManager展开的了。

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    

    这里根据前一章的内容先要对DriverManager进行初始化,也就是调用了一个loadInitialDrivers()函数。

    private static void loadInitialDrivers() {
            .....
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
    
                    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);// 1
                    Iterator<Driver> driversIterator = loadedDrivers.iterator();
    
                    try{
                        while(driversIterator.hasNext()) {
                            driversIterator.next();
                        }
                    } catch(Throwable t) {
                    // Do nothing
                    }
                    return null;
                }
            });
            .....
    }
    

    从这一小段中,我们关注注释1能够知道他专门去访问了一个ServiceLoader的类,点进去之后我们能够发现这么三段代码。

    // 1
    public static <S> ServiceLoader<S> load(Class<S> service) {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            return ServiceLoader.load(service, cl);
    }
    
    // 2
    public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
            return new ServiceLoader<>(service, loader);
        }
    
    // 3
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
            service = Objects.requireNonNull(svc, "Service interface cannot be null");
            loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
            acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
            reload();
    }
    

    由1 --> 2 --> 3的顺序循序渐进,你是否已经和我关注到一个问题了!!
    ClassLoader.getSystemClassLoader(),看到这个函数了吗,我在上文提到过,这个函数我们获得的类加载器将会是应用程序类加载器。也就是说我们的任务不会再向上传递了,到头就是到了应用程序类加载器这个位置,那么双亲委派模型也就破坏了。

    以上就是打破双亲委派的方法之一的介绍了。

    溯源ClassLoader.getSystemClassLoader()

    为什么说我们调用的是应用程序类加载器呢?
    接下来直接从源码来解析了。
    首先就是调用getSystemClassLoader()这个函数了

    这张图里我们只用关注圈红的函数。


    然后在initSystemClassLoader()函数中调用了一个Launcher的类。

    Launcher整个类的创建,想来读者也已经看到loader这个变量了,通过getAppClassLoader()这个函数所创建的loader也就是我们口中所说的应用程序类加载器了。

    以上就是我的学习成果,如果有什么我没有思考到的地方或是文章内存在错误,欢迎与我分享。


    相关文章推荐:
    JVM必备基础知识(一)-- 类的加载机制
    JVM必备基础知识(三)-- GC垃圾回收机制

    相关文章

      网友评论

        本文标题:JVM必备基础知识(二)-- 类加载器和双亲委派模型

        本文链接:https://www.haomeiwen.com/subject/lqdgxhtx.html