ClassLoader知识

来源:互联网 发布:淘宝网手提电脑 编辑:程序博客网 时间:2024/05/16 06:40

(1)ClassLoader基本概念

与C或C++编写的程序不同,Java程序并不是一可执行文件,而是由许多独立的类文件组成的,每个文件对应一个Java类。此外,这些类文件并非全部装入内存,而是根据程序需要逐渐载入。ClassLoader是JVM实现的一部分,ClassLoader包括bootstrap classloader(启动类加载器),ClassLoader在JVM运行的时候加载Java核心的API,以满足Java程序最基本的需求,其中就包括用户定义的ClassLoader,这里所谓的用户定义,是指通过Java程序实现的两个ClassLoader:一个是ExtClassLoader(称为扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar),它的作用是用来加载Java的扩展API,也就是/lib/ext中的类;第二个是AppClassLoader(称为系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件),它是用来加载用户机器上CLASSPATH设置目录中的Class的,通常在没有指定ClassLoader的情况下,程序员自定义的类就由该ClassLoader加载。

注意: 除了Java默认提供的三个ClassLoader之外,用户还可以根据需要定义自已的ClassLoader,而这些自定义的ClassLoader都必须继承自java.lang.ClassLoader类,也包括Java提供的另外二个ClassLoader(Extension ClassLoader和App ClassLoader)在内,但是Bootstrap ClassLoader不继承自ClassLoader,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。

(2)ClassLoader的原理介绍

ClassLoader层次结构:


UML类图:


下面看一下ClassLoader中的一端源码

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    /**
     * 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:
     *
     * <p><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> 
     */
    protected synchronized Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
    {
    // First, check if the class has already been loaded
    Class c = findLoadedClass(name);
    if (c == null) {
        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.
            c = findClass(name);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
    }

从上面的源码可以看出,ClassLoader使用的是双亲委托模型来搜索类的,每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。

(3)为什么要使用这种父类委托模式呢

   因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。

(4)ClassLoader加载流程


当运行一个程序的时候,JVM启动,运行bootstrap classloader,该ClassLoader加载Java核心API(ExtClassLoader和AppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一个程序最基本的加载流程。

  1. 调用 findLoadedClass(String) 来检查是否已经加载类。
  2. 在父类加载器上调用 loadClass 方法。如果父类加载器为 null,则使用虚拟机的内置类加载器。
  3. 调用 findClass(String) 方法查找类,这个方法在ClassLoader中并未实现,由其子类负责实现。findClass()的功能是找到class文件并把字节码加载到内存中。

(5)一些重要的方法

a、loadClass方法

ClassLoader.loadClass()是ClassLoader的入口点。该方法的定义如下:

Class loadClass(String  name,boolean  resole);

name是指JVM需要的类的名称,如Foo或java.lang.Object。resolve参数告诉方法是否需要解析类。在准备执行之前,应考虑类解析。注意:并不总是需要解析,如果JVM只需要知道该类是否存在或找出该类额超类,那么就不需要解析。

b、defineClass方法

defineClass方法接受由原始字节组成的数组,并把它转换成Class对象。原始数组包含如从文件系统或网络装入的数据。defineClass管理JVM的许多复杂的实现层面——它把字节码分析成运行时数据结构、校验有效性等。因为defineClass方法被标记成final的,所以也不能覆盖它。

c、findSystemClass方法

findSystemClass方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用defineClass将原始字节转换成Class对象,以将该文件转换成类。当运行Java应用程序时,这是JVM正常装入类的默认机制。对于定制的ClassLoader,只有在尝试其他方法装入之后,再使用findSystemClass。这是因为ClassLoader是负责执行装入类的相关步骤,不负责所有类的所有信息。例如,即使ClassLoader从远程的Web站点装入了某些类,仍然需要在本地机器上装入大量的基本Java库。而这些类库不是我们所关心的,所以要JVM以默认方式从本地文件系统装入它们,这就是findSystemClass的用途。

d、resolveClass方法

正如前面提到的,可以不完全(不带解析)装入类,也可以完全地装入类。当编写我们自己的loadClass时,可以调用resolveClass,这取决于loadClass的resolve参数的值。

e、findLoadedClass方法

findLoadClass充当一个缓存:当请求装入类时,它调用该方法来查看ClassLoader是否已装入这个类,这样可以避免重新装入已存在的类所造成的麻烦。

f、findClass方法

loadClass默认实现调用这个新方法。findClass的用途包含ClassLoader的所有特殊代码,而无须复制器代码(例如,当专门的方法失败时,调用系统ClassLoader)。

目的是从本地文件系统使用实现的类加载器装载一个类。为了创建自己的类装载器,应该扩展ClassLoader类,这是一个抽象类。可以创建一个FileClassLoaderextends ClassLoader,然后覆盖ClassLoader中的findClass(String name)方法,这个方法通过类的名字得到一个Class对象。

public Class findClass(String name)

{

byte [] data=loadClassData(name);

return defineClass(name,data,0,data.length);

}

g、getSystemClassLoader方法

如果覆盖findClass或loadClass,getSystemClassLoader能以实际的ClassLoader对象来访问系统ClassLoader(而不是固定地从findSystemClass调用它)。为了将类请求委托给父类ClassLoader,这个新方法允许ClassLoader获取它的父类ClassLoader.当使用特殊方法是,定制的ClassLoader不能找到类时,可以使用这种方法。

父类ClassLoader被定义成创建该ClassLoder所包含代码的 对象的ClassLoader.

h、forName方法

Class类中有一个静态方法forName,这个方法和ClassLoader中的loadClass方法的目的一样,都是用来加载class的,但是两者在作用上却有区别。

关于forName()方法
这个方法总是返回要加载的类的Class类的实例
1、forName(String className)单参数时, initialize=true
    a.总是使用当前类装载器(也就是装载执行forName()请求的类  的类装载器)
    b.总是初始化这个被装载的类(当然也包括:装载、连接、初始化)
2、forName(String className, boolean initialize, ClassLoader loader)
    a.loader指定装载参数类所用的类装载器,如果null则用bootstrp装载器。
    b.initialize=true时,肯定连接,而且初始化了;
    c.false时,绝对不会初始化,但是可能被连接了,但是这里有个例外,如果在调用这个forName()前,已经被初始化了(当然,这里也暗含着:className是被同一个loader所装载的,即被参数中的loader所装载的,而且这个类被初始化了),那么返回的类型也肯定是被初始化的

关于用户自定义的类装载器的loadClass()方法
1、loadClass(String name)单参数时, resolve=false
    a.如果这个类已经被这个类装载器所装载,那么,返回这个已经被装载的类型的Class的实例,否则,就用这个自定义的类装载器来装载这个class,这时不知道是否被连接。绝对不会被初始化
    b.这时唯一可以保证的是,这个类被装载了。但是不知道这个类是不是被连接和初始化了
2、loadClass(String name, boolean resolve)
    a.resolve=true时,则保证已经装载,而且已经连接了。resolve=falses时,则仅仅是去装载这个类,不关心是否连接了,所以此时可能被连接了,也可能没有被连接。

另外:这里所谓的“初始化”是指类的初始化,即执行了className字节码的<clinit>方法

再者:类是这样加载的

1、装载

2、连接

                a)验证-->检查类格式等

                b)准备-->给类变量分配内存,并根据类变量类型设置默认值(即内存中置0)

                c)解析-->常量池解析

3、初始化

即执行Java代码的字节码的<clinit>方法,给类变量赋予程序员需要的值



测试你所使用的JVM的ClassLoader

import java.net.URL;public class Sample {    public static void main(String[] args)     {            ClassLoader cloader;             cloader = ClassLoader.getSystemClassLoader();             System.out.println(cloader);             while (cloader != null )             {                 cloader = cloader.getParent();                 System.out.println(cloader);             }             try             {             Class<Object> c1 = (Class<Object>)Class.forName("java.lang.Object" );            cloader = c1.getClassLoader();             System.out.println( "java.lang.Object's loader is " + cloader);             Class<Sample> c2 = (Class<Sample>) Class.forName("Sample" );            cloader = c2.getClassLoader();             System.out.println( " Sample's loader is " + cloader);             }            catch (Exception e)             {                  e.printStackTrace();            }     }}

参考来源:http://www.cnblogs.com/dragonsuc/p/4378877.html

http://blog.csdn.net/iceman1952/article/details/1523025

http://blog.csdn.net/xyang81/article/details/7292380

http://blog.csdn.net/vking_wang/article/details/17162327


1 0