ClassLocader类介绍
为了完成加载类的这个职责,ClassLoader 提供了一系列的方法,比较重要的方法如下表所示。

getParent()

返回该类加载器的父类加载器。

loadClass(String name)

加载名称为 name 的类,返回的结果是 java.lang.Class 类的实例。

findClass(String name)

查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。

findLoadedClass(String name)

查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class 类的实例。

defineClass(String name, byte[] b, int off, int len)

把字节数组 b 中的内容转换成 Java 类,返回的结果是 java.lang.Class 类的实例。这个方法被声明为 final 的。

resolveClass(Class c)

链接指定的 Java 类。


类加载器的分类
Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。

系统提供的类加载器主要有下面三个:

1) 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自java.lang.ClassLoader。
2) 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
3) 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以过 ClassLoader.getSystemClassLoader() 来获取它。
除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader 类的方式实现自己的类加载器,以满足一些特殊的需求。

除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过 getParent() 方法可以得到。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器



类加载过程
真正完成类的加载工作是通过调用defineClass 来实现的;而启动类的加载过程是通过调用loadClass 来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。如类com.example.Outer 引用了类com.example.Inner,则由类 com.example.Outer 的定义加载器负责启动类com.example.Inner 的加载过程。

方法 loadClass() 抛出的是 java.lang.ClassNotFoundException 异常;方法defineClass() 抛出的是 java.lang.NoClassDefFoundError 异常。

类加载器在成功加载某个类之后,会把得到的 java.lang.Class 类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即loadClass 方法不会被重复调用。



类加载代理机制
了解了这一点之后,就可以理解代理模式的设计动机了。代理模式是为了保证 Java 核心库的类型安全。所有 Java 应用都至少需要引用 java.lang.Object 类,也就是说在运行的时候,java.lang.Object 这个类需要被加载到 Java 虚拟机中。如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object类,而且这些类之间是不兼容的。通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。

不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。这种技术在许多框架中都被用到。


Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。比如一个 Java 类com.example.Sample,编译之后生成了字节代码文件 Sample.class。两个不同的类加载器 ClassLoaderA 和 ClassLoaderB 分别读取了这个 Sample.class 文件,并定义出两个 java.lang.Class 类的实例来表示这个类。这两个实例是不相同的。对于 Java 虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常ClassCastException。
1. Java 基础类由 BL(Bootstrap Loader) 在虚拟机启动时一次性载入。
2. 包含 main() 的入口类由 AL 的 loadClass() 方法载入。
3. 由 new 关键字创建一个类的实例。该类由运行时刻包含该 new 语句的类实例的类装载器( ClassLoader.getCallerClassLoader() )的 loadClass() 方法载入。
4. 调用 Class.forName() 方法。完整的 forName() 函数版本有一个 ClassLoader 参数,用于指定用什么类装载器来装载指定类。
public static Class forName(String name, boolean initialize,
ClassLoader loader) throws ClassNotFoundException
对于 public static Class forName(String className) 版本,是由运行时刻包含该语句的类实例的类装载器( ClassLoader.getCallerClassLoader() )的 loadClass() 方法载入。
5. 调用某个 ClassLoader 实例的 loadClass() 方法。通过该 ClassLoader 实例的 loadClass() 方法载入。应用程序可以通过继承 ClassLoader 实现自己的类装载器。
6 .装载一个类时,首先要装载该类的基类及其接口。

所有类都由类装载器载入,载入内存中的类对应一个 java.lang.Class 实例。
已被加载的类由该类的类加载器实例与该类的全路径名的组合标识。设有 packagename.A Class ,分别被类加载器 CL1 和 CL2 加载,则系统中有两个不同的 java.lang.Class 实例:
存在一个 Bootstrap Loader (以下简称为 BL ),由 C++ 写成,负责在虚拟机启动后一次性加载 Java 基础类库中的所有类。其他的类装载器由 Java 写成,都是 java.lang.ClassLoader 的子类。
除 BL 之外的所有类装载器都有一个 parent 属性,指向其父装载器。查阅 java.lang.ClassLoader 的源码,不难发现类装载器的委托装载方式。

    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 = findBootstrapClass0(name);
                   }
              } catch (ClassNotFoundException e) {
                  // If still not found, then invoke findClass in order
                  // to find the class.
                  c = findClass(name);
              }
          }
          if (resolve) {
              resolveClass(c);
          }
          return c;
    }

对于给定的类名,首先检查自己是否已加载过该类。如果没有,则首先通过父装载器加载(如果 parent==null ,则直接通过 BL 来加载,相当于 BL 是其父装载器)。如果父装载器也无法装载,才真正调用自己的 findClass() 方法来装载。

Java 程序启动过程中的类装载器
当执行“ java XXX.class ”时, java.exe 首先找到 JRE ( Java Runtime Environment ),接着找到位于 JRE 之中的 jvm.dll ,最后载入 jvm.dll 并启动虚拟机。
虚拟机一启动,先做一些初始化动作,如获取系统参数等,然后产生 BL 。 BL 加载 Java 基础类,这些类都存放在 JRE 中的 lib 目录下,可由 System.getProperty(“sun.boot.class.path”) 列出,如:
C:\Program Files\Java\jre1.5.0_09\lib\rt.jar;
C:\Program Files\Java\jre1.5.0_09\lib\i18n.jar;
C:\Program Files\Java\jre1.5.0_09\lib\sunrsasign.jar;
C:\Program Files\Java\jre1.5.0_09\lib\jsse.jar;
C:\Program Files\Java\jre1.5.0_09\lib\jce.jar;
C:\Program Files\Java\jre1.5.0_09\lib\charsets.jar;
C:\Program Files\Java\jre1.5.0_09\classes
BL 然后创建 sun.misc.Launcher$ExtClassLoader ( ExtClassLoader 是定义于 sun.misc.Launcher 之内的内部类,继承自 java.lang.URLClassLoader )的实例(以下简称 EL )和 sun.misc.Launcher$AppClassLoader ( AppClassLoader 是定义于 sun.misc.Launcher 之内的内部类,继承自 URLClassLoader )的实例(以下简称 AL ),并将 EL 的 parent 属性设置为 null , AL 的 parent 属性设置为 EL 。
EL 在程序运行过程中按需加载保存在 JRE 的“ \lib\ext ”目录下的类。该目录可由 System.getProperty(“java.ext.dirs”) 读出,如
C:\Program Files\Java\jre1.5.0_09\lib\ext
AL 在程序运行过程中按需加载的类搜索路径则是从系统参数 java.class.path 取出的字符串。 java.class.path 是由我们在执行 java.exe 时,利用 -cp 或 -classpath 或 CLASSPATH 环境变量所决定。我们应用程序用到的非 JRE 提供类的搜索路径一般都配置在 java.class.path 中。

参考链接: http://www.blogjava.net/RogerTwain/archive/2007/03/07/102287.html
http://blog.csdn.net/cxhzqhzq/article/details/6686121

标签: none

添加新评论