对Java类加载器的认识

来源:互联网 发布:什么是优化发展环境 编辑:程序博客网 时间:2024/04/20 10:48

       java是具有动态性,什么是动态性?有个最直观的例子:windows系统的即插即用,支持即插即用的设备可以在系统不重新启动的情况下既可以热把插使用。而java的动态性表现在:我们的程序可以不用全盘的重新编译就能对程序某部分进行更新,C#也和java一样具有动态性,而且它的这种动态性表现更为直观:直接生成windows的动态连接库文件——dll文件。而java生成的是class文件,class是怎么实现动态性的了,这个时候就全靠我们今天的主角:java的类加载器。

       我们都知道所有的java类都是继承了object这个类,在object这个类中有一个方法:getclass(),这个方法返回的是一个Class类对象。

       一旦一个类被载入JVM中,同一个类就不会被再次载入了(切记,同一个类)。这里存在一个问题就是什么是“同一个类”?正如一个对象有一个具体的状态,即标识,一个对象始终和其代码(类)相关联(见文认识java的Class类)。同理,载入JVM的类也应该有一个具体的标识,我们知道:在JAVA中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名是包名和类名。不过在JVM中一个类是用其全名再附加上一个加载类ClassLoader的实例作为唯一标识。因此,如果一个名为Pg的包中,有一个名为Cl的类,被类加载器KlassLoader的一个实例对象kl1加载,生成Cl的对象,即C1.class(这里指类,而非对象)在JVM中表示为(Cl, Pg, kl1)。这意味着两个类加载器的实例(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不同的,被它们所加载的类也因此完全不同,互不兼容的。

       在java中每个类都是由某个类加载器的实体来载入的,因此在Class类的实体中,都会有字段记录载入它的类加载器的实体(当为null时,其实是指Bootstrap ClassLoader)。 在java类加载器中除了引导类加载器(既Bootstrap ClassLoader),所有的类加载器都有一个父类加载器(因为他们本身自己就是java类)。而类的加载机制是遵循一种委托模式:当类加载器有加载类的需求时,会先请求其Parent加载(依次递归),如果在其父加载器树中都没有成功加载该类,则由当前类加载器加载。
        java的类加载器分为以下几种:
        1,Bootstrap ClassLoader,用C++实现,一切的开始,是所有类加载器的最终父加载器。负责将一些关键的Java类,如java.lang.Object和其他一些运行时代码先加载进内存中。
        2,ExtClassLoader,用java实现,是Launcher.java的内部类,编译后的名字为:Launcher$ExtClassLoader.class 。此类由Bootstrap ClassLoader加载,但由于Bootstrap ClassLoader已经脱离了java体系(c++),所以Launcher$ExtClassLoader.class的Parent(父加载器)被设置为null;它用于装载Java运行环境扩展包(jre/lib/ext)中的类,而且一旦建立其加载的路径将不再改变。
         3,AppClassLoader,用java实现,也是是Launcher.java的内部类,编译后的名字为:Launcher$AppClassLoader.class 。AppClassLoader是当Bootstrap ClassLoader加载完ExtClassLoader后,再被Bootstrap ClassLoader加载。所以ExtClassLoader和AppClassLoader都是被Bootstrap ClassLoader加载,但AppClassLoader的Parent被设置为ExtClassLoader。可见Parent和由哪个类加载器来加载不一定是对应的。
个类装载器是我们经常使用的,可以调用ClassLoader.getSystemClassLoader() 来获得,如果程序中没有使用类装载器相关操作设定或者自定义新的类装载器,那么我们编写的所有java类都会由它来装载。而它的查找区域就是我们常常说到的Classpath,一旦建立其加载路径也不再改变。
         4,ClassLoader:一般我们自定义的ClassLoader从ClassLoader类继承而来。比如:URLClassloader是ClassLoader的一个子类,而URLClassloader也是ExtClassLoader和AppClassLoader的父类(注意不是父加载器)。


     让我们先回到java的动态性,实现java的动态性有两种方法类型:一种是隐式,另一种是显式。什么是隐式?new 这个关键字我们都认识,当我们用其将类实例化时(即将对象载入),这种就是隐式!我们再来看显式的实现方法,一种可以由java.long.Class里面的forName()方法将类实例化,其中也用到了类加载器,详情见:认识java的Class类,另一种是由也就是直接用类加载器ClassLoader来实现。
       ClassLoader一些重要的方法
        A)  方法 loadClass 
        ClassLoader.loadClass() 是 ClassLoader 的入口点。该方法的定义如下: 
        Class loadClass( String name, boolean resolve );
        参数name  JVM 需要的类的名称,如 Person 或 java.lang.Object。
        参数 resolve 参数告诉方法是否需要解析类。在准备执行类之前,应考虑类解析。并不总是需要解析。如果 JVM 只需要知道该类是否存在或找出该类的超类,那么就不需要解析详情见:认识java的Class类 中的forName()方法的介绍。

         B)  方法 defineClass 
       defineClass 方法是 ClassLoader 的主要诀窍。该方法接受由原始字节组成的数组并把它转换成 Class 对象。原始数组包含如从文件系统或网络装入的数据。defineClass 管理 JVM 的许多复杂、神秘和倚赖于实现的方面 -- 它把字节码分析成运行时数据结构、校验有效性等等。不必担心,您无需亲自编写它。事实上,即使您想要这么做也不能覆盖它,因为它已被标记成final的。 
       C)  方法 findSystemClass 
         findSystemClass 方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用 defineClass 将原始字节转换成 Class 对象,以将该文件转换成类。当运行 Java 应用程序时,这是 JVM 正常装入类的缺省机制。(Java 2 中 ClassLoader 的变动提供了关于 Java 版本 1.2 这个过程变动的详细信息。) 对于定制的 ClassLoader,只有在尝试其它方法装入类之后,再使用 findSystemClass。原因很简单:ClassLoader 是负责执行装入类的特殊步骤,不是负责所有类。例如,即使 ClassLoader 从远程的 Web 站点装入了某些类,仍然需要在本地机器上装入大量的基本 Java 库。而这些类不是我们所关心的,所以要 JVM 以缺省方式装入它们:从本地文件系统。这就是 findSystemClass 的用途。 
       D) 方法 resolveClass 
       正如前面所提到的,可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的 loadClass 时,可以调用 resolveClass,这取决于 loadClass 的 resolve 参数的值。 
      E) 方法 findLoadedClass 
       findLoadedClass 充当一个缓存:当请求 loadClass 装入类时,它调用该方法来查看 ClassLoader 是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法。

怎么组装这些方法 
  1) 调用 findLoadedClass 来查看是否存在已装入的类。
  2) 如果没有,那么采用那种特殊的神奇方式来获取原始字节。
  3) 如果已有原始字节,调用 defineClass 将它们转换成 Class 对象。
  4) 如果没有原始字节,然后调用 findSystemClass 查看是否从本地文件系统获取类。
  5) 如果 resolve 参数是 true,那么调用 resolveClass 解析 Class 对象。
  6) 如果还没有类,返回 ClassNotFoundException。

Java 2 中 ClassLoader 的变动
1)loadClass 的缺省实现 
   定制编写的 loadClass 方法一般尝试几种方式来装入所请求的类,如果您编写许多类,会发现一次次地在相同的、很复杂的方法上编写变量。 在 Java 1.2 中 loadClass 的实现嵌入了大多数查找类的一般方法,并使您通过覆盖 findClass 方法来定制它,在适当的时候 findClass 会调用 loadClass。 这种方式的好处是您可能不一定要覆盖 loadClass;只要覆盖 findClass 就行了,这减少了工作量。 

2)新方法:findClass
     loadClass 的缺省实现调用这个新方法。findClass 的用途包含您的 ClassLoader 的所有特殊代码,而无需要复制其它代码(例如,当专门的方法失败时,调用系统 ClassLoader)。 

3) 新方法:getSystemClassLoader 
     如果覆盖 findClass 或 loadClass,getSystemClassLoader 使您能以实际 ClassLoader 对象来访问系统 ClassLoader(而不是固定的从 findSystemClass 调用它)。 
  
4) 新方法:getParent  
    为了将类请求委托给父代 ClassLoader,这个新方法允许 ClassLoader 获取它的父代 ClassLoader。当使用特殊方法,定制的 ClassLoader 不能找到类时,可以使用这种方法。
父代 ClassLoader 被定义成创建该 ClassLoader 所包含代码的对象的 ClassLoader。


 Class类是为了保存JAVA虚拟机运行时(RTTI)对所有对象进行类型识别的信息而设立的.   
  当然Class也是继承自Object类的,每个类都有Class对象,想得到一个类的Class对象共有三种方法.   
  1:调用getClass()   
  Employee emp;   
  Class   cls=emp.getClass();   
  2:静态方法forName(String   )   
  String   className="Employee";   
  Class   cls=Class.forName(className);   
  3:class成员变量法   
  Class   cls=Employee.class;  


0 0