详解Java类加载机制

想聊Java的类加载机制就离不开Java类加载器,这是Java语言的一个很重要的创新点,曾经也是Java流行的重要原因。当初引入这个机制是为了满足Java Applet开发的需求,简单而言,就是为了能够执行从从远程下载过来的的Java类,JVM咬咬牙引入了Java类加载机制,后来的基于jvm的动态部署,插件化开发包括大家热议的热修复(热修复其实也有不基于ClassLoader的解决方案,有兴趣请看我的热修复初探),总之很多后来的技术都源于在JVM中引入了类加载器。

JVM:很惭愧,就做了一点微小的工作,谢谢大家。

加载器

好了,讲完了ClassLoader的来由,接下来可以正是介绍一下类加载器。如你所知,当你写完了一个.java文件的时候,编译器会把他编译成一个由字节码组成的class文件,当程序运行时,JVM会首先寻找包含有main()方法的类,把这个class文件中的字节码数据读入进来,转化成JVM中运行时对应的Class对象。执行这个动作的,就叫类加载器。

  • ClassLoader:是Java层几乎所有类加载器的父类,它定义了加载器的基本行为和加载动作

分类

类加载器分为可以大致分为:

  • Bootstrap ClassLoader(启动类加载器)
    • 这个类加载器负责将一些核心的,被JVM识别的类加载进来,用C++实现,与JVM是一体的。
  • Extension ClassLoader(扩展类加载器)
    • 这个类加载器用来加载 Java 的扩展库
  • Applicaiton ClassLoader(应用程序类加载器)
    • 用于加载我们自己定义编写的类
  • User ClassLoader (用户自己实现的加载器)
    • 当实际需要自己掌控类加载过程时才会用到,一般没有用到。

与之配套的加载机制就是“双亲委派模型”:

双亲委派模型

先看看Java类加载器的体系结构:


B55F.tmp.png

类加载逻辑代码:

 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 {
                    //如果class没有被加载且已经设置parent,那么请求其父加载器加载
                    if (parent != null) {
                        /**
                         *注意当这里调用parent.loadClass()方法找不到Class时会抛出ClassNotFoundException异常,但是该异常是被捕获的
                         */
                        c = parent.loadClass(name, false);
                    } else {
                      //如果没有设定parent类加载器,则寻找BootstrapClss并尝试使用Boot loader加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                /**
                 *如果当前这个loader所有的父加载器以及顶层的Bootstrap ClassLoader都不能加载待加载的类
                 *那么则调用自己的findClass()方法来加载
                 */                
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }


“双亲委派模型”简单来说就是:

  • 1.先检查需要加载的类是否已经被加载,如果没有被加载,则委托父加载器加载,父类继续检查,尝试请父类加载,这个过程是从下-------> 上;
  • 2.如果走到顶层发现类没有被加载过,那么会从顶层开始往下逐层尝试加载,这个过程是从上 ------> 下;

需要注意的几个问题:

  • 1,双亲XX 这种说法是有问题的,因为Java世界一直是单亲家庭
  • 2,事实上加载器之间不是通过继承,而是通过组合的方式来实现整个加载过程,即每个加载器都持有上层加载器的引用,所以父加载器是一种笼统的说法

这里必须要提一提JVM如何判定两个类你是否相等:

  • JVM除了比较类是否相等还要比较加载这两个类的类加载器是否相等,只有同时满足条件,两个类才能被认定是相等的。

接下来问题来了,为什么双亲委派模型要有三层加载器而不是一层?

实际上,三层类加载器代表了JVM对于待加载类的三个信任层次,当需要加载一个全限定名为java.lang.Object的类时,JVM会首先信任顶层的引导类加载器,即优先用这个加载器尝试加载,如果不行,JVM会选择继续信任第二层的拓展类加载器,往下,知道三层都无法加载,JVM才会选择信任开发者自己定义的加载器。这种”父类“优先的加载次序有效的防止了恶意代码的加载。

总结

总而言之,双亲委派模型有效解决了以下问题:

  • 每一个类都只会被加载一次,避免了重复加载
  • 每一个类都会被尽可能的加载(从引导类加载器往下,每个加载器都可能会根据优先次序尝试加载它)
  • 有效避免了某些恶意类的加载(比如自定义了Java。lang.Object类,一般而言在双亲委派模型下会加载系统的Object类而不是自定义的Object类)

tips:可以说双亲委派模型主要是为了维护Java类加载的安全,防止恶意加载,与此配套的还有命名空间出有效的隔离,命名空间的作用抽象理解就是

  • 竖直方向上,父加载器中加载的类对于所有子加载器可见
  • 水平方向上,子类之间各自加载的类对于各自是不可间的(达到隔离效果)

基本上,日常的开发使用的都是使用系统提供的类加载器依照“双亲委派模型”来加载的,开发者基本接触不到加载过程。但是当你要动态加载自己的外部的类的时候,比如从网络上下载的class文件,就需要自定义classloader来实现加载过程。

在Android中,QQZone团队提出的基于Dex分包的热修复解决方案就属于加载外部的类,本来应当由开发者自己实现classloader来实现加载过程,但是Android本身已经为我们封装好了一个classloader,就是DexClassLoader(贴心~~)

事实上,如今Java中很多插件化开发,动态部署,热修复等动态技术都是基于Java的类加载器来展开的。因此,我才会想专门用一篇文章总结Java的类加载器和加载机制。后面我会找时间基于HotFix详细的分析其中的类加载过程。毕竟理论总要落实到代码才会让人印象深刻。

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

推荐阅读更多精彩内容