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

本章内容是对《深入理解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垃圾回收机制

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容