• 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神。

  • 相关阅读:
    网页加速的14条优化法则 网站开发与优化
    .NET在后置代码中输入JS提示语句(背景不会变白)
    C语言变量声明内存分配
    SQL Server Hosting Toolkit
    An established connection was aborted by the software in your host machine
    C语言程序设计 2009春季考试时间和地点
    C语言程序设计 函数递归调用示例
    让.Net 程序脱离.net framework框架运行
    C语言程序设计 答疑安排(2009春季 110周) 有变动
    软件测试技术,软件项目管理 实验时间安排 2009春季
  • 原文地址:https://www.cnblogs.com/xiaozhang2014/p/8098893.html
Copyright © 2020-2023  润新知