首页投稿(暂停使用,暂停投稿)程序员Android技术知识

详解Java类加载机制

2016-09-03  本文已影响1240人  拉丁吴

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

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

加载器

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

分类

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

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

双亲委派模型

先看看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;
        }
    }


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

需要注意的几个问题:

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

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

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

总结

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

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

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

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

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

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

上一篇下一篇

猜你喜欢

热点阅读