深入JAVA虚拟机之类加载器

前言:
虚拟机设计团队把类加载阶段中"通过一个类的权限定名来获取描述此类的二进制字节流"这个动作放到虚拟机外部区实现,让程序自己决定如何去获取所需的类。实现这个动作的代码模块就被称为类加载器

它最初是为了满足Java Applet的需求而被开发,而现在Java Applet基本已经宣布死亡,但类加载器却在类层次划分、OSGi、热部署、代码加密等领域大放异彩。它是java技术体系中一块重要的基石。



对于任意一个类,都需要由加载该类的加载器和这个类本身一同确立在虚拟机中的唯一性。通俗说就是:比较两个类是否相等,这两个类必须是同一个类加载器的前提才有意义。否则,就算两个类是来源同一个class文件,只要加载器不同,那么这两个类必定是不相等的。

备注:
这里所指的"相等", 包括:Class对象的equals方法、isAssignableForm方法、isInstance方法的返回结果,也包括instanceof关键字做对象所属关系判定等情况。

双亲委派模型

在虚拟机的角度看,只有两类类加载器:启动类加载器、其他类加载器。启动类加载器是属于虚拟机中的一部分,是用C++实现的,而其他类加载器则是java实现的。在开发者眼中看来,类加载器可以分的更加细致些:
启动类加载器:
这个类加载器,负责把放在JAVA_HOME\lib目录中的,或者被-Xbootclasspath参数指定的路径中的,并且是虚拟机识别的类库加载到虚拟机中。启动类加载器无法被java程序直接引用。

扩展类加载器:
它是负责JAVA_HOME\lib\ext目录中的,或者被java.ext.dirs系统变量指定的所有类库,开发者可以直接使用扩展类加载器。

应用程序类加载器:
这个类加载器是由sun,misc.Launcher$AppClassLoader实现的。因为这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般都会称为系统类加载器。它负责加载用户类路径上的(ClassPath)上所指定的类库。开发者可以直接使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般这个就是程序中默认的类加载器。

备注:
我们的应用程序都是由这三种类加载器相互配合进行加载的,如果有必要,还可以加入自己的类加载器。类加载器与自定义类加载器之间的关系如下图:

双亲委派模型,规定了,每一个类加载器(除了启动类加载器)都必须有父类,并且这种关系不是继承实现,而是通过组合实现的,而且要求当调用了当前类加载器时,必须先给父类去加载,如果没有父类,那么就交给启动类加载器加载,如果父类返回无法加载,则给当前类加载器加载,如果当前也无法加载,则抛出ClassNotFoundException.

这样做的好处,就会有一个优先级,确保一个类只会被一个类加载器加载如虚拟机中,如果都是优先当前,那么就会可能出现同一个类在虚拟机中被不同的类加载器加载了多遍,并且这多个类还是不相等的。

双亲委派的代码都在java.lang.ClassLoader的loadClass方法中,我们可以通过分析源代码就可以知道:

/**
     * Loads the class with the specified <a href="#name">binary name</a>.  The
     * default implementation of this method searches for classes in the
     * following order:
     *
     * <ol>
     *
     *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
     *   has already been loaded.  </p></li>
     *
     *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
     *   on the parent class loader.  If the parent is <tt>null</tt> the class
     *   loader built-in to the virtual machine is used, instead.  </p></li>
     *
     *   <li><p> Invoke the {@link #findClass(String)} method to find the
     *   class.  </p></li>
     *
     * </ol>
     *
     * <p> If the class was found using the above steps, and the
     * <tt>resolve</tt> flag is true, this method will then invoke the {@link
     * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
     *
     * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
     * #findClass(String)}, rather than this method.  </p>
     *
     * <p> Unless overridden, this method synchronizes on the result of
     * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
     * during the entire class loading process.
     *
     * @param  name
     *         The <a href="#name">binary name</a> of the class
     *
     * @param  resolve
     *         If <tt>true</tt> then resolve the class
     *
     * @return  The resulting <tt>Class</tt> object
     *
     * @throws  ClassNotFoundException
     *          If the class could not be found
     */
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

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

破坏双亲委派模型
双亲委派模型不是一个强制性的约束模型,只是java设计者们推荐给开发者的类加载器实现方式。绝大部分都是遵循这个模型的。但截止到深入java虚拟机作者编写这本书时就已经有三次破坏该模型。

第一次:
是出现在双亲委派模型之前,因为jdk1.2之后才引入双亲委派模型。而类加载器和抽象类java.lang.ClassLoader早在jdk1.0就出现了。为了兼容双亲委派模型之前的类加载器,java设计团队不得不妥协,给jdk1.2之后的java.lang.ClassLoader类新添加了一个protected的findClass方法,在此之前,用户去继承java.lang.ClassLoader就是为了重写loadClass()方法,因为虚拟机在进行类加载的时候会调用类加载器的 一个私有方法loadClassInternal(),而此方法唯一逻辑就是调用自己的loadClass()方法。前面通过loadClass源码,我们可以知道如果父类加载失败,才会去调用findClass()方法定义的类加载过程。这样就可以保证满足双亲委派模型。

第二次:
第二次是因为双亲模型自身的缺陷,虽然双亲委派模型很好的解决了各个类加载器的基础类的统一,但如果基础类又要调用回用户的代码呢?比如说:JDNI服务,JDNI服务是java的标准服务,它的代码是有启动类加载器加载,但JDNI的目的是为了对资源进行集中管理和查找,它需要调用其他独立厂商实现并部署在应用程序的ClassPath下的JDNI接口提供者的代码,但启动类并不认识这些代码。为了解决这类问题,java设计团队,提供了一种并不优雅的设计:线程上下文类加载器。这个类加载器可以通过java.lang.Thread类的setContextCLassLoader()方法进行设置,如果没有创建线程时设置,它会从父线程中继承一个,如果应用程序全局都没有设置,那么这个类加载器就是应用程序类加载器。

JDNI服务使用了这个线程上下文加载器去加载所需的SPI代码,也就是父类加载器请求子类加载器去完成类加载动作。java中设计SPI的加载动作基本都采用这种方式,比如:JDNI、JDBC、JAXB、JCE和JBI等。

第三次:
第三次是由于用户对程序动态性的追求导致的,这里说的动态性是指:代码热替换、模块热部署等,说白了就是希望应用程序能像电脑外设那样,插上鼠标,键盘,不用重启机器。热部署对于企业生产环境的诱惑是毋庸置疑的。

原文地址:http://blog.51cto.com/4837471/2158530

时间: 2024-10-09 02:44:53

深入JAVA虚拟机之类加载器的相关文章

【深入理解Java虚拟机 】类加载器的命名空间以及类的卸载

类加载器的命名空间 每个类加载器又有一个命名空间,由其以及其父加载器组成 类加载器的命名空间的作用和影响 每个类加载器又有一个命名空间,由其以及其父加载器组成 在每个类加载器自己的命名空间中不能出现相同类名的类 (此处值得是类的全名,包含包名) 在不同的类命名空间中,可能会出现多个相同的类名的类 如下面的代码示例中, 首先定义一个类加载器 MyClassLoader static class MyClassLoader extends ClassLoader { private String c

Java中的类加载器

转载:http://blog.csdn.net/zhangjg_blog/article/details/16102131 从java的动态性到类加载机制 我们知道,Java是一种动态语言.那么怎样理解这个"动态"呢?或者说一门语言具备了什么特性,才能称之为动态语言呢?对于java,我是这样理解的. 我们都知道JVM(java虚拟机)执行的不是本地机器码指令,而是执行一种称之为字节码的指令(存在于class文件中).这就要求虚拟机在真正执行字节码之前,先把相关的class文件加载到内存

JAVA基础_类加载器

什么是类加载器 类加载器是Java语言在1.0版本就引入的.最初是为了满足JavaApplet需要.现在类加载器在Web容器和OSGI中得到了广泛的应用,一般来说,Java应用的开发人员不需要直接同类加载器进行交互.Java虚拟机默认的行为就已经足够满足大多数情况的需求了.不过如果遇到了需要与类加载器进行交互的情况,而对类加载器的机制又不是很了解的话,就很容易花大量的时候在ClassNotFoundException和NoClassDefFoundError等异常上. 顾名思义,类加载器是用来加

JAVA之了解类加载器Classloader

1.类的加载.连接和初始化   类初始化通常包括加载.连接.初始化三个步骤. (1)进程的结束 每当运行一个java程序时,将会启动一个java虚拟机进程,不管程序多么复杂,有多少线程,都在这个java虚拟机进程里.以下四种情况会使得该进程被终止-- 程序运行到最后正常结束: 程序里遭遇了System.exit(),或者是Runtime.getRunTime().exit()代码: 程序执行中遇到了未捕获的异常或者错误: java所在平台强制结束了JVM进程: 当该进程结束,那么该进程在内存中的

Java虚拟机的类加载机制

Java虚拟机类加载过程是把Class类文件加载到内存,并对Class文件中的数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程. 在加载阶段,java虚拟机需要完成以下3件事: a.通过一个类的全限定名来获取定义此类的二进制字节流. b.将定义类的二进制字节流所代表的静态存储结构转换为方法区的运行时数据结构. c.在java堆中生成一个代表该类的java.lang.Class对象,作为方法区数据的访问入口. Java虚拟机的类加载是通过类加载器实现的, Java中

黑马程序员——【Java高新技术】——类加载器

一.概述 (一)类加载器(class loader) 用来动态加载Java类的工具,它本身也是Java类. (二)类加载器作用 负责加载 Java 类的字节代码到 Java 虚拟机中. (三)Java类加载器 1.Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类加载器负责加载特定位置的类: (1)BootStrap(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader.

深入JAVA虚拟机之类加载机制

前言: 前面学习了类Class文件格式和里面具体的内容,也已经学习了运行时数据区的各部分区域的内容.接下来就是学习JVM是如何把Class文件中记录的信息加载到运行时内存中的,以及class文件中各个部分的信息分别存放在运行时数据区的什么地方.从这篇文字中我们能获得什么? 1.虚拟机是如何加载Class文件的 2.Class文件信息进入JVM后有那些变化 3.进一步理解运行时数据区.Class文件信息.以及类加载过程中都做了那些操作. java语言特性的根基 类加载机制 虚拟机把描述类的数据从C

【深入理解Java虚拟机】类加载机制

本文内容来源于<深入理解Java虚拟机>一书,非常推荐大家去看一下这本书. 本系列其他文章: [深入理解Java虚拟机]Java内存区域模型.对象创建过程.常见OOM [深入理解Java虚拟机]垃圾回收机制 1.类加载机制概述 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 在java中,类型的加载.连接和初始化过程都是在程序运行期间完成的,这种策略虽然会带来一些性能开销,但是却为jav

深入理解Java虚拟机笔记---类加载过程

一.加载 "加载"(Loading)阶段是"类加载"(Class Loading)过程的一个阶段.在加载阶段,虚拟机需要完成以下三件事情: a.通过一个类的全限制名来获取定义此类的二进制字节流. b.将这个字节流所代表的静态存储结构转化为方法区的运行进数据结构. c.在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口. 虚拟机规范的这三点要求实际上并不具体,因此虚拟机实现与具体应用的灵活度相当大.例如"通过一