zoukankan      html  css  js  c++  java
  • JVM-类加载过程

    一般来说,Java源代码(.java)经过编译器编译成字节码(.class)后,类加载器读取字节码文件,最终加载并转换成 java.lang.Class类的一个实例。

    Java中的类加载器大致分为两种,一种是系统提供的,另外一种是Java开发者开发的。而系统提供的类加载器主要有三个:

    • 引导类加载器(Bootstrap Class Loader):最顶层的类加载器,主要加载核心的类库,JRE目录下的rt.jar,resources.jar,charsets.jar等jar包和class,一般是用原生C或C++来实现的。
    • 扩展类加载器(Extension Class Loader):用来加载JRElibext目录下的jar包和class等,由ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现;
    • 系统类加载器(System Class Loader),也可以成为应用类加载器:加载Java当前应用CLASSPATH下的所有类,由AppClassLoader(sun.misc.Launcher$AppClassLoader)实现,一般情况下,它是Java应用程序默认的类加载器。

    除了系统提供的类加载器外,开发者可以通过继承java.lang.ClassLoader类或组合的形式来实现自己的类加载器,以满足一些特殊的需求。

    JVM要求除了最上层的引导类加载器之外,所有的类加载器都应当由一个父类加载器,而这个父加载器可以通过下表给出的getParent方法得到。这种加载模式就是所谓的双亲委派模式,如下图所示:

    除了Bootstrap加载器,基本上所有的类加载器都是java.lang.ClassLoader类的一个实例。

    ClassLoader类大致说明:

      java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例。除此之外,ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等。不过本文只讨论其加载类的功能。为了完成加载类的这个职责,ClassLoader提供了一系列的方法,比较重要的方法如下:

    表示类名称的 name参数的值是类的二进制名称。需要注意的是内部类的表示,如 com.example.Sample$1和 com.example.Sample$Inner等表示方式。

    每一个Java类都维护着一个指向定义它的类加载器的引用,通过 getClassLoader()方法就可以获取到此引用。所以说这种类加载器间的父子关系一般都是通过组合,而不是继承来实现的。

    通过一个小程序来看看:

    public class ClassLoaderTest {
        public static void main(String[] args) throws Exception {
            ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
            while (classLoader != null) {
                System.out.println(classLoader.toString());
                classLoader = classLoader.getParent();
            }
        }
    }

    输出:

    sun.misc.Launcher$AppClassLoader@18b4aac2
    sun.misc.Launcher$ExtClassLoader@1540e19d

    可以看到,第一个输出的是ClassLoaderTest类的类加载器,即系统类加载器,它是 sun.misc.Launcher$AppClassLoader@18b4aac2类的实例;第二个是扩展类加载器,是 sun.misc.Launcher$ExtClassLoader@1540e19d 类的实例。需要注意的是,没有输出引导类的加载器,因为由于bootstrap的实现不是Java,所以JDK源码中,对于父加载器是bootstrap的情况下,getParent方法返回的是null。

      让我们来看一下双亲委派模式的简易流程:

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

    1.先检查需要加载的类是否已经被加载,如果没有被加载,则委托父加载器加载,父类继续检查,尝试请父类加载,这个过程是从下-------> 上;

    2.如果走到顶层发现类没有被加载过,那么会从顶层开始往下逐层尝试加载,这个过程是从上 ------> 下;

    3.如果最终都加载不了,那就会抛出异常;

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

      Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。比如一个 Java 类 com.example.Sample,编译之后生成了字节代码文件 Sample.class。两个不同的类加载器 ClassLoaderA和 ClassLoaderB分别读取了这个 Sample.class文件,并定义出两个 java.lang.Class类的实例来表示这个类。这两个实例是不相同的。对于 Java 虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常 ClassCastException。

      对于两个相同名称的类而言,不同的类加载器为相同名称的类创建了额外的命名空间,不同类加载器间加载的类之间是不兼容的。命名空间的作用抽象理解就是:

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

    这就解释了另外一个问题,为什么JVM使用双亲委派模式,主要为了保证 Java 核心库的类型安全,防止重复与恶意加载;
      比如Java应用都至少要引用java.lang.Object类,也就是说在运行的时候,java.lang.Object这个类需要被加载到 Java 虚拟机中。如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object类,而且这些类之间是不兼容的。

      并且如果要加载一个System类,使用委托机制,会递归的向父类查找,最终都是委托给最顶层的启动类加载器进行加载也就是用Bootstrap尝试加载,如果加载不了再向下查找。如果能在Bootstrap中找到然后加载,然后缓存下来,如果此时另一个类也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回缓存中的System即可而不需要重新加载,从而避免了恶意加载。

    线程上下文类加载器(Context Class Loader)

     还有一种类加载器,被称为Context  Class Loader,线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。ContextClassLoader并不是一种新的类加载器,而是一种抽象的说法,它的获取和设置可以通过Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl) 来实现。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。

    ContextClassLoader的存在是为了解决双亲委派机制无法解决的问题。双亲委派机制委托链上面的图已经说过了,一般说来,处于委托链下层的classLoader可以很容易的使用上层classLoader所加载的类;而反过来,如果上层的classLoader要使用下层的classLoader所加载的类的话,由于双亲委派机制是单向的,所以无法通过双亲委派来实现,所以就有了ContextClassLoader,这种情况下就可以把某个位于委派链下层的ClassLoader设置为线程的contextClassLoader,这种情况及突破了双亲委派的限制了。

      其实,BootstrapClassLoader、ExtClassLoader、AppClassLoader实际是查阅相应的环境属性sun.boot.class.path、java.ext.dirs和java.class.path来加载资源文件的。他们的加载可以通过一个关键字来说明:路径;

    public class ClassLoaderTest {
        public static void main(String[] args) throws Exception {
            System.out.println(System.getProperty("sun.boot.class.path"));
            System.out.println("=======================");
            System.out.println(System.getProperty("java.ext.dirs"));
            System.out.println("==========================");
            System.out.println(System.getProperty("java.class.path"));
        }
    }

    结果:

    E:softwareJDK8.0jrelib
    esources.jar;
    E:softwareJDK8.0jrelib
    t.jar;
    E:softwareJDK8.0jrelibsunrsasign.jar;
    E:softwareJDK8.0jrelibjsse.jar;
    E:softwareJDK8.0jrelibjce.jar;
    E:softwareJDK8.0jrelibcharsets.jar;
    E:softwareJDK8.0jrelibjfr.jar;
    E:softwareJDK8.0jreclasses
    =======================
    E:softwareJDK8.0jrelibext;
    C:WindowsSunJavalibext
    ==========================
    E:softwareJDK8.0jrelibcharsets.jar;
    E:softwareJDK8.0jrelibdeploy.jar;
    E:softwareJDK8.0jrelibextaccess-bridge-64.jar;
    E:softwareJDK8.0jrelibextsunpkcs11.jar;
    ......
    E:softwareJDK8.0jrelibextzipfs.jar;
    E:softwareJDK8.0jrelibjavaws.jar;
    E:softwareJDK8.0jrelibjce.jar;
    E:softwareJDK8.0jrelibjfr.jar;
    E:softwareJDK8.0jrelibjfxswt.jar;
    E:softwareJDK8.0jrelibjsse.jar;
    E:softwareJDK8.0jrelibmanagement-agent.jar;
    E:softwareJDK8.0jrelibplugin.jar;
    E:softwareJDK8.0jrelib
    esources.jar;
    E:softwareJDK8.0jrelib
    t.jar;
    C:UsersIdeaProjectsuntitledoutproductionJavaTest;
    E:softwareideaIntelliJ IDEA 2017.1.3libidea_rt.jar

    由于CLASSPATH下jar包太多,省略了一部分;不过,从打印的内容我们也可以看到他们加载的资源情况。

    继承体系如下,classLoader的入口是:sun.misc.Launcher,其中ExtClassLoader和AppClassLoader是Launcher的内部静态类;

    主要看下ClassLoader的loadClass方法,其他的源码等下篇文章再写:

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // 首先,检查这个类是否已经被加载了
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                    // 如果class没有被加载且已经设置parent,那么请求其父加载器加载
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                        //如果没有设定parent类加载器,则寻找BootstrapClss并尝试使用Boot loader加载
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // 如果父加载器找不到class时会抛出ClassNotFoundException异常
                        
                    }
    
                    if (c == null) {
                        // 如果当前这个loader所有的父加载器以及顶层的Bootstrap ClassLoader都不能加载待加载的类
                        // 那么则调用自己的findClass()方法来加载
                        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;
            }
        }

    注意:Class.forName使用的是哪个类加载器?

    首先,class.forName是带参数的,可以指定类加载器,如果没有指定,那默认使用的是当前类的类加载器,也就是默认的AppClassLoader;

    public class ClassLoaderTest {
        public static void main(String[] args) throws Exception {
            ClassLoader test = ClassLoaderTest.class.getClassLoader();
            System.out.println(test);
    
            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
            System.out.println(contextClassLoader);
    
            ClassLoader forNameClassLoader = Class.forName("com.test.ForNameTest").getClassLoader();
            System.out.println(forNameClassLoader);
        }
    }
    class ForNameTest{}
    sun.misc.Launcher$AppClassLoader@18b4aac2
    sun.misc.Launcher$AppClassLoader@18b4aac2
    sun.misc.Launcher$AppClassLoader@18b4aac2

    参考自:https://www.ibm.com/developerworks/cn/java/j-lo-classloader/

    http://www.blogjava.net/zhuxing/archive/2008/08/08/220841.html

    http://blog.csdn.net/briblue/article/details/54973413

    感谢RednaxelaFX,R神。

  • 相关阅读:
    [书籍精读]《JavaScript异步编程》精读笔记分享
    [技术翻译]在现代JavaScript中编写异步任务
    [技术翻译]Web网页内容是如何影响电池使用寿命的?
    [技术翻译]使用Nuxt生成静态网站
    [Vue源码]一起来学Vue模板编译原理(二)-AST生成Render字符串
    [Vue源码]一起来学Vue双向绑定原理-数据劫持和发布订阅
    [Vue源码]一起来学Vue模板编译原理(一)-Template生成AST
    [技术翻译]您应该知道的13个有用的JavaScript数组技巧
    css清除默认样式
    [小技巧]让你的GridView支持IQueryable,并自动实现真分页
  • 原文地址:https://www.cnblogs.com/xiaozhang2014/p/8098893.html
Copyright © 2011-2022 走看看