详解Java类加载机制

作者: 拉丁吴 | 来源:发表于2016-09-03 01:14 被阅读1240次

    想聊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详细的分析其中的类加载过程。毕竟理论总要落实到代码才会让人印象深刻。

    相关文章

      网友评论

        本文标题:详解Java类加载机制

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