精华内容
下载资源
问答
  • 深入理解ClassLoader

    2019-02-11 17:51:00
    深入理解ClassLoader 深入理解ClassLoader ClassLoader 作用 负责将 Class 加载到 JVM 中 ClassLoader主要对类的请求提供服务,当JVM需要某类时,它根据名称向ClassLoader要求这个类,然后由...

    深入理解ClassLoader

     

    深入理解ClassLoader

    ClassLoader 作用

    • 负责将 Class 加载到 JVM 中
      • ClassLoader主要对类的请求提供服务,当JVM需要某类时,它根据名称向ClassLoader要求这个类,然后由ClassLoader返回 这个类的class对象。
    • 审查每个类由谁加载(父优先的等级加载机制)
    • 将 Class 字节码重新解析成 JVM 统一要求的对象格式

    类加载时机与过程

    • 类从被加载到虚拟机内存中开始,直到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析这三个部分统称为连接(linking)
    • enter image description here

    类的实例化与类的初始化

    • 类的实例化是指创建一个类的实例(对象)的过程;
    • 类的初始化是指为类中各个类成员(被static修饰的成员变量)赋初始值的过程,是类生命周期中的一个阶段。

    什么情况下需要开始类加载过程的第一个阶段:”加载”。

    • 虚拟机规范中并没强行约束,这点可以交给虚拟机的的具体实现自由把握,但是对于初始化阶段虚拟机规范是严格规定了如下几种情况,如果类未初始化会对类进行初始化:
      • 创建类的实例
      • 对类进行反射调用的时候,如果累没有进行过初始化,则需要先触发其初始化
      • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
      • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main 方法的那个类),虚拟机会先初始化这个主类
      • 当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化。

    双亲委托模型

    • JVM 在运行时会产生三个ClassLoader,Bootstrap ClassLoader、Extension ClassLoader和 App ClassLoader。
    • Bootstrap ClassLoader是用C++编写的,在Java中看不到它,是null。它用来加载核 心类库,就是在lib下的类库,
    • Extension ClassLoader加载lib/ext下的类库,
    • App ClassLoader加载 Classpath里的类库,
    • 三者的关系为:App ClassLoader的Parent是Extension ClassLoader,而 Extension ClassLoader的Parent为Bootstrap ClassLoader。
    • 所有系统中的ClassLoader组成一棵树,ClassLoader在载入类库时先让Parent寻找,Parent找不到才自己找。
    • 加载一个类时,首先BootStrap进行寻 找,找不到再由Extension ClassLoader寻找,最后才是App ClassLoader。
    • 将 ClassLoader设计成委托模型的一个重要原因是出于安全考虑,比如在Applet中,如果编写了一个java.lang.String类并具有破 坏性。
      假如不采用这种委托机制,就会将这个具有破坏性的String加载到了用户机器上,导致破坏用户安全。

    线 程中的ClassLoader

    • 每个运行中的线程都有一个成员contextClassLoader,用来在运行时动态地载入其它类

    Web应用中的ClassLoader

    • 回到上面的例子,在Tomcat 里,WebApp的ClassLoader的工作原理有点不同,它先试图自己载入类(在ContextPath/WEB-INF/...中载入类),如果 无法载入,再请求父ClassLoader完成。
    • 对于WEB APP线程,它的contextClassLoader是WebAppClassLoader
      对于Tomcat Server线程,它的contextClassLoader是CatalinaClassLoader

    获得ClassLoader的几种方法

    • 可以通过如下3种方法得到ClassLoader
    this.getClass.getClassLoader(); // 使用当前类的ClassLoader  
    Thread.currentThread().getContextClassLoader(); // 使用当前线程的ClassLoader  
    ClassLoader.getSystemClassLoader(); // 使用系统ClassLoader,即系统的入口点所使用的ClassLoader。
    (注意,system ClassLoader与根 ClassLoader并不一样。JVM下system ClassLoader通常为App ClassLoader) 

    参考

     
     
     
     
     
     
     
    posted @ 2019-02-11 17:51 节奏型男-全栈 阅读(...) 评论(...) 编辑 收藏
    展开全文
  • 深入理解 classloader

    2018-05-30 04:28:00
    2019独角兽企业重金招聘Python工程师标准>>> ...

    https://blog.csdn.net/javazejian/article/details/73413292

    这篇文章讲得非常详细,膜拜。

    转载于:https://my.oschina.net/u/232911/blog/1820918

    展开全文
  • 深入理解classLoader

    2016-04-12 16:25:53
    最近在研究osgi,在osgi里面里面有个很重要的东西,就是ClassLoader,所以,在网上搜集了一些资料,整理一下, 并加入了自己的一些理解; (1)jvm的装载过程以及装载原理 所谓装载就是寻找一个类或是一个接口的...

    原文:http://blog.csdn.net/turkeyzhou/article/details/2792876

    最近在研究osgi,在osgi里面里面有个很重要的东西,就是ClassLoader,所以,在网上搜集了一些资料,整理一下,
    并加入了自己的一些理解;

    (1)jvm的装载过程以及装载原理
    所谓装载就是寻找一个类或是一个接口的二进制形式并用该二进制形式来构造代表这个类或是这个接口的class对象的过程,
    其中类或接口的名称是给定了的。当然名称也可以通过计算得到,但是更常见的是通过搜索源代码经过编译器编译后所得到
    的二进制形式来构造。 在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,
    其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:

      装载:查找和导入类或接口的二进制数据; 
      链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的; 
      校验:检查导入类或接口的二进制数据的正确性; 
      准备:给类的静态变量分配并初始化存储空间; 
      解析:将符号引用转成直接引用; 
      初始化:激活类的静态变量的初始化Java代码和静态Java代码块。

    (2):java中的类是什么?
    一个类代表要执行的代码,而数据则表示其相关状态。状态时常改变,而代码则不会。当我们将一个特定的状态与一个类相对应起来,也就意味着将一个类事例化。尽管相同的类对应的实例其状态千差万别,但其本质都对应着同一段代码。在JAVA中,一个类通常有着一个.class文件,但也有例外。在JAVA的运行时环境中(Java runtime),每一个类都有一个以第一类(first-class)的Java对象所表现出现的代码,其是java.lang.Class的实例。我们编译一个JAVA文件,编译器都会嵌入一个public, static, final修饰的类型为java.lang.Class,名称为class的域变量在其字节码文件中。因为使用了public修饰,我们可以采用如下的形式对其访问:
    java.lang.Class klass = Myclass.class;
    一旦一个类被载入JVM中,同一个类就不会被再次载入了(切记,同一个类)。这里存在一个问题就是什么是“同一个类”?正如一个对象有一个具体的状态,即标识,一个对象始终和其代码(类)相关联。同理,载入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)是不同的,被它们所加载的类也因此完全不同,互不兼容的。那么在JVM中到底有多少种类加载器的实例?下一节我们揭示答案。

    (3):java的几种ClassLoader:
    在java中,我们可以取得这么以下三个ClassLoader类:
    一.    ClassLoader基本概念

    1.ClassLoader分类

    类装载器是用来把类(class)装载进JVM的。

    JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader)。

    JVM在运行时会产生三个ClassLoader:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader。Bootstrap是用C++编写的,我们在Java中看不到它,是null,是JVM自带的类装载器,用来装载核心类库,如java.lang.*等。

    AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent为Bootstrap ClassLoader。

     

    Java提供了抽象类ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。 System Class Loader是一个特殊的用户自定义类装载器,由JVM的实现者提供,在编程者不特别指定装载器的情况下默认装载用户类。系统类装载器可以通过ClassLoader.getSystemClassLoader() 方法得到。

     

     

    例1,测试你所使用的JVM的ClassLoader

    /*LoaderSample1.java*/

    public   class  LoaderSample1 {

         public   static   void  main(String[] args) {

            Class c;

            ClassLoader cl;

            cl  =  ClassLoader.getSystemClassLoader();

            System.out.println(cl);

             while  (cl  !=   null ) {

                cl  =  cl.getParent();

                System.out.println(cl);

            }

             try  {

                c  =  Class.forName( " java.lang.Object " );

                cl  =  c.getClassLoader();

                System.out.println( " java.lang.Object's loader is  "   +  cl);

                c  =  Class.forName( " LoaderSample1 " );

                cl  =  c.getClassLoader();

                System.out.println( " LoaderSample1's loader is  "   +  cl);

            }  catch  (Exception e) {

                e.printStackTrace();

            }

        }

    }


    在我的机器上(Sun Java 1.4.2)的运行结果

    sun.misc.Launcher$AppClassLoader@1a0c10f

    sun.misc.Launcher$ExtClassLoader@e2eec8

    null

    java.lang.Object's loader is null

    LoaderSample1's loader is sun.misc.Launcher$AppClassLoader@1a0c10f

    第一行表示,系统类装载器实例化自类sun.misc.Launcher$AppClassLoader

    第二行表示,系统类装载器的parent实例化自类sun.misc.Launcher$ExtClassLoader

    第三行表示,系统类装载器parent的parent为bootstrap

    第四行表示,核心类java.lang.Object是由bootstrap装载的

    第五行表示,用户类LoaderSample1是由系统类装载器装载的

    注意,我们清晰的看见这个三个ClassLoader类之间的父子关系(不是继承关系),父子关系在ClassLoader的实现中有一个ClassLoader类型的属性,我们可以在自己实现自定义的ClassLoader的时候初始化定义,而这三个系统定义的ClassLoader的父子关系分别是

    AppClassLoader——————》(Parent)ExtClassLoader——————————》(parent)BootClassLoader(null c++实现)

    系统为什么要分别指定这么多的ClassLoader类呢?
    答案在于因为java是动态加载类的,这样的话,可以节省内存,用到什么加载什么,就是这个道理,然而系统在运行的时候并不知道我们这个应用与需要加载些什么类,那么,就采用这种逐级加载的方式
    (1)首先加载核心API,让系统最基本的运行起来
    (2)加载扩展类
    (3)加载用户自定义的类


    package org.corey.clsloader;

    import java.net.MalformedURLException;
    import java.net.URL;
    import java.net.URLClassLoader;

    import sun.net.spi.nameservice.dns.DNSNameService;

    public class ClsLoaderDemo {

     /**
      * @param args
      */
     public static void main(String[] args) {
      
      System.out.println(System.getProperty("sun.boot.class.path"));
      System.out.println(System.getProperty("java.ext.dirs"));
      System.out.println(System.getProperty("java.class.path"));
     }
    }


    程序结果为:
    E:/MyEclipse 6.0/jre/lib/rt.jar;E:/MyEclipse 6.0/jre/lib/i18n.jar;E:/MyEclipse 6.0/jre/lib/sunrsasign.jar;E:/MyEclipse 6.0/jre/lib/jsse.jar;E:/MyEclipse 6.0/jre/lib/jce.jar;E:/MyEclipse 6.0/jre/lib/charsets.jar;E:/MyEclipse 6.0/jre/classes
    E:/MyEclipse 6.0/jre/lib/ext
    E:/workspace/ClassLoaderDemo/bin

    在上面的结果中,你可以清晰看见三个ClassLoader分别加载类的路径;也知道为什么我们在编写程序的时候,要把用到的jar包放在工程的classpath下面啦,也知道我们为什么可以不加载java.lang.*包啦!其中java.lang.*就在rt.jar包中;

    (4)ClassLoader的加载机制:
    现在我们设计这种一下Demo:

    package java.net;

    public class URL {
     private String path;

     public URL(String path) {
      this.path = path;
     }

     public String toString() {
      return this.path + " new Path";
     }
    }


    package java.net;

    import java.net.*;

    public class TheSameClsDemo {

     /**
      * @param args
      */
     public static void main(String[] args) {
      URL url = new URL("http://www.baidu.com");
      System.out.println(url.toString());
     }

    }
    在这种情况下,系统会提示我们出现异常,因为我们有两个相同的类,一个是真正的URL,一个是我在上面实现的伪类;出现异常是正常的,因为你想想,如果我们在执行一个applet的时候,程序自己实现了一个String的类覆盖了我们虚拟机上面的真正的String类,那么在这个String里面,不怀好意的人可以任意的实现一些功能;这就造成极不安全的隐患;所以java采用了一种名为“双亲委托”的加载模式;

    以下是jdk源代码:

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

    在上面的代码中,我们可以清晰的看见,我们调用一个ClassLoader加载程序的时候,这个ClassLoader会先调用设置好的parent ClassLoader来加载这个类,如果parent是null的话,则默认为Boot ClassLoader类,只有在parent没有找的情况下,自己才会加载,这就避免我们重写一些系统类,来破坏系统的安全;

    再来看一个明显的例子:
    package org.corey;

    public class MyCls{
     private String name;
     
     public MyCls(){
     
     }

     public MyCls(String name){
     this.name=name;
     }
      
     public void say(){
     System.out.println(this.name); 
     }
    }
    把上面这个MyCls类打成jar包,丢进ext classLoader的加载路径;

    然后写出main类:
    package org.corey.clsloader;

    import org.corey.MyCls;

    public class TheSameClsDemo {

     /**
      * @param args
      */
     public static void main(String[] args) {
      MyCls myClsOb=new MyCls("name");
         myClsOb.say(); 
         System.out.println(MyCls.class.getClassLoader());
         System.out.println(System.getProperty("java.class.path"));
         System.out.println(TheSameClsDemo.class.getClassLoader());
     }
    }

    并且把MyCls类加入biild-path里面方便引用;
    结果是:
    name
    sun.misc.Launcher$ExtClassLoader@16930e2
    E:/workspace/ClassLoaderDemo/bin;E:/MyEclipse 6.0/jre/lib/ext/corey.jar
    sun.misc.Launcher$AppClassLoader@7259da

    从上面的例子可以清晰的看出ClassLoader之间的这种双亲委托加载模式;

    再来看下一个例子(摘自http://bbs.cnw.com.cn/viewthread.php?tid=95389)
    下面我们就来看一个综合的例子。首先在eclipse中建立一个简单的java应用工程,然后写一个简单的JavaBean如下:
    package classloader.test.bean;

     

    publicclass TestBean {

     


    public TestBean() {}


    }
    在现有当前工程中另外建立一测试类(ClassLoaderTest.java)内容如下:
    测试一:


    publicclass ClassLoaderTest {

     


    publicstaticvoid main(String[] args) {

     


    try {

     


    //查看当前系统类路径中包含的路径条目

     

                System.out.println(System.getProperty("java.class.path"));

     

    //调用加载当前类的类加载器(这里即为系统类加载器)加载TestBean

     

    Class typeLoaded = Class.forName("classloader.test.bean.TestBean");

     

    //查看被加载的TestBean类型是被那个类加载器加载的

     

                System.out.println(typeLoaded.getClassLoader());

     

            } catch (Exception e) {

     

                e.printStackTrace();

     

            }

     

        }


    }
    对应的输出如下:


    D:"DEMO"dev"Study"ClassLoaderTest"bin


    sun.misc.Launcher$AppClassLoader@197d257
    (说明:当前类路径默认的含有的一个条目就是工程的输出目录)
    测试二:
    将当前工程输出目录下的…/classloader/test/bean/TestBean.class打包进test.jar剪贴到< Java_Runtime_Home >/lib/ext目录下(现在工程输出目录下和JRE扩展目录下都有待加载类型的class文件)。再运行测试一测试代码,结果如下:


    D:"DEMO"dev"Study"ClassLoaderTest"bin


    sun.misc.Launcher$ExtClassLoader@7259da
    对比测试一和测试二,我们明显可以验证前面说的双亲委派机制,系统类加载器在接到加载classloader.test.bean.TestBean类型的请求时,首先将请求委派给父类加载器(标准扩展类加载器),标准扩展类加载器抢先完成了加载请求。

    测试三:
    将test.jar拷贝一份到< Java_Runtime_Home >/lib下,运行测试代码,输出如下:


    D:"DEMO"dev"Study"ClassLoaderTest"bin


    sun.misc.Launcher$ExtClassLoader@7259da

    测试三和测试二输出结果一致。那就是说,放置到< Java_Runtime_Home >/lib目录下的TestBean对应的class字节码并没有被加载,这其实和前面讲的双亲委派机制并不矛盾。虚拟机出于安全等因素考虑,不会加载< Java_Runtime_Home >/lib存在的陌生类,开发者通过将要加载的非JDK自身的类放置到此目录下期待启动类加载器加载是不可能的。做个进一步验证,删除< Java_Runtime_Home >/lib/ext目录下和工程输出目录下的TestBean对应的class文件,然后再运行测试代码,则将会有ClassNotFoundException异常抛出。有关这个问题,大家可以在java.lang.ClassLoader中的loadClass(String name, boolean resolve)方法中设置相应断点运行测试三进行调试,会发现findBootstrapClass0()会抛出异常,然后在下面的findClass方法中被加载,当前运行的类加载器正是扩展类加载器(sun.misc.Launcher$ExtClassLoader),这一点可以通过JDT中变量视图查看验证。

    (5)被不同的ClassLoader加载的两个类之间有什么限制和不同?
    现在我们来看一下一个现象:
    在eclipse里面我是这样做的:
    OneCls.java

    package org.corey.one;

    import org.corey.two.TwoCls;

    public class OneCls {

     public OneCls() {
      System.out.println();
      TwoCls two = new TwoCls();
      two.say();
     }

    }

    TwoCls.java

    package org.corey.two;

    public class TwoCls {
     
     public void say() {
      System.out.println("i am two");
     }
    }

    Demo.java:

    package org.corey.Demo;

    import org.corey.one.OneCls;

    public class Demo {

     /**
      * @param args
      */
     public static void main(String[] args) {
      OneCls one=new OneCls();
     }
    }

    在这里,我们来仔细看下,one引用了two,demo引用了one,这是三个类都是由AppClassLoader加载的;运行正常;

    把OneCls打成jar包,放在lib/ext路径下面,然后在工程里面引入这个jar包;运行:异常,这是因为:


    Demo是由AppClassLoader载入,委托给双亲加载失败后,由AppClassLoader加载,而加载OneCls的时候,委托给双亲,被ExtClassLoader加载成功,但是在载入OneCls的时候,同时引用了TwoCls,但是ExtClassLoader引用TwoCls失败,但是他只会委托给双亲,而不会委托给AppClassLoader这个儿子,所以会出现异常;


    3. 奇怪的隔离性

    我们不难发现,图2中的类装载器AA和AB, AB和BB,AA和B等等位于不同分支下,他们之间没有父子关系,我不知道如何定义这种关系,姑且称他们位于不同分支下。两个位于不同分支的类装载器具有隔离性,这种隔离性使得在分别使用它们装载同一个类,也会在内存中出现两个Class类的实例。因为被具有隔离性的类装载器装载的类不会共享内存空间,使得使用一个类装载器不可能完成的任务变得可以轻而易举,例如类的静态变量可能同时拥有多个值(虽然好像作用不大),因为就算是被装载类的同一静态变量,它们也将被保存不同的内存空间,又例如程序需要使用某些包,但又不希望被程序另外一些包所使用,很简单,编写自定义的类装载器。类装载器的这种隔离性在许多大型的软件应用和服务程序得到了很好的应用。下面是同一个类静态变量为不同值的例子。

    package test;
    public class A {
      public static void main( String[] args ) {
        try {
          //定义两个类装载器
          MyClassLoader aa= new MyClassLoader();
          MyClassLoader bb = new MyClassLoader();

          //用类装载器aa装载testb.B类
          Class clazz=aa.loadClass("testb. B");
          Constructor constructor= 
            clazz.getConstructor(new Class[]{Integer.class});
          Object object = 
         constructor.newInstance(new Object[]{new Integer(1)});
          Method method = 
         clazz.getDeclaredMethod("printB",new Class[0]);

          //用类装载器bb装载testb.B类
          Class clazz2=bb.loadClass("testb. B");
          Constructor constructor2 = 
            clazz2.getConstructor(new Class[]{Integer.class});
          Object object2 = 
         constructor2.newInstance(new Object[]{new Integer(2)});
          Method method2 = 
         clazz2.getDeclaredMethod("printB",new Class[0]);

          //显示test.B中的静态变量的值 
          method.invoke( object,new Object[0]);
          method2.invoke( object2,new Object[0]);
        } catch ( Exception e ) {
          e.printStackTrace();
        }
      }
    }

     

    //Class B 必须位于MyClassLoader的查找范围内,
    //而不应该在MyClassLoader的父类装载器的查找范围内。
    package testb;
    public class B {
        static int b ;

        public B(Integer testb) {
            b = testb.intValue();
        }

        public void printB() {
            System.out.print("my static field b is ", b);
        }
    }

     

    public class MyClassLoader extends URLClassLoader{
      private static File file = new File("c://classes ");
      //该路径存放着class B,但是没有class A

      public MyClassLoader() {
        super(getUrl());
      }

      public static URL[] getUrl() {
        try {
          return new URL[]{file.toURL()};
        } catch ( MalformedURLException e ) {
          return new URL[0];
        }
      }
    }

    程序的运行结果为:

    my static field b is 1
    my static field b is 2

    程序的结果非常有意思,从编程者的角度,我们甚至可以把不在同一个分支的类装载器看作不同的java虚拟机,因为它们彼此觉察不到对方的存在。程序在使用具有分支的类装载的体系结构时要非常小心,弄清楚每个类装载器的类查找范围,尽量避免父类装载器和子类装载器的类查找范围中有相同类名的类(包括包名和类名),下面这个例子就是用来说明这种情况可能带来的问题。

     

    (6) 类如何被装载及类被装载的方式(转自Java类装载体系中的隔离性  作者:盛戈歆)

    在java2中,JVM是如何装载类的呢,可以分为两种类型,一种是隐式的类装载,一种式显式的类装载。

    2.1 隐式的类装载

    隐式的类装载是编码中最常用得方式:

    A b = new A();

    如果程序运行到这段代码时还没有A类,那么JVM会请求装载当前类的类装器来装载类。问题来了,我把代码弄得复杂一点点,但依旧没有任何难度,请思考JVM得装载次序:

    package test;
    Public class A{
        public void static main(String args[]){
            B b = new B();
        }
    }

    class B{C c;}

    class C{}

    揭晓答案,类装载的次序为A->B,而类C根本不会被JVM理会,先不要惊讶,仔细想想,这不正是我们最需要得到的结果。我们仔细了解一下JVM装载顺序。当使用Java A命令运行A类时,JVM会首先要求类路径类装载器(AppClassLoader)装载A类,但是这时只装载A,不会装载A中出现的其他类(B类),接着它会调用A中的main函数,直到运行语句b = new B()时,JVM发现必须装载B类程序才能继续运行,于是类路径类装载器会去装载B类,虽然我们可以看到B中有有C类的声明,但是并不是实际的执行语句,所以并不去装载C类,也就是说JVM按照运行时的有效执行语句,来决定是否需要装载新类,从而装载尽可能少的类,这一点和编译类是不相同的。

    2.2 显式的类装载

    使用显示的类装载方法很多,我们都装载类test.A为例。

    使用Class类的forName方法。它可以指定装载器,也可以使用装载当前类的装载器。例如:

    Class.forName("test.A");
    它的效果和
    Class.forName("test.A",true,this.getClass().getClassLoader());
    是一样的。

    使用类路径类装载装载.

    ClassLoader.getSystemClassLoader().loadClass("test.A");

    使用当前进程上下文的使用的类装载器进行装载,这种装载类的方法常常被有着复杂类装载体系结构的系统所使用。

    Thread.currentThread().getContextClassLoader().loadClass("test.A")

    使用自定义的类装载器装载类

    public class MyClassLoader extends URLClassLoader{
    public MyClassLoader() {
            super(new URL[0]);
        }
    }
    MyClassLoader myClassLoader = new MyClassLoader();
    myClassLoader.loadClass("test.A");

    MyClassLoader继承了URLClassLoader类,这是JDK核心包中的类装载器,在没有指定父类装载器的情况下,类路径类装载器就是它的父类装载器,MyClassLoader并没有增加类的查找范围,因此它和类路径装载器有相同的效果。

     

    (7)ClassLoader的一些方法实现的功能:
    方法 loadClass

     

     

    ClassLoader.loadClass() 是 ClassLoader 的入口点。其特征如下:


    Class loadClass( String name, boolean resolve ); name 参数指定了 JVM 需要的类的名称,该名称以包表示法表示,如 Foo 或 java.lang.Object。

    resolve 参数告诉方法是否需要解析类。在准备执行类之前,应考虑类解析。并不总是需要解析。如果 JVM 只需要知道该类是否存在或找出该类的超类,那么就不需要解析。

    在 Java 版本 1.1 和以前的版本中,loadClass 方法是创建定制的 ClassLoader 时唯一需要覆盖的方法。(Java 2 中 ClassLoader 的变动提供了关于 Java 1.2 中 findClass() 方法的信息。)

     


    方法 defineClass


    defineClass 方法是 ClassLoader 的主要诀窍。该方法接受由原始字节组成的数组并把它转换成 Class 对象。原始数组包含如从文件系统或网络装入的数据。

    defineClass 管理 JVM 的许多复杂、神秘和倚赖于实现的方面 -- 它把字节码分析成运行时数据结构、校验有效性等等。不必担心,您无需亲自编写它。事实上,即使您想要这么做也不能覆盖它,因为它已被标记成最终的。

    你可以看见native标记,知道defineClass是一个jni调用的方法,是由c++实现数据到内存的加载的;

     


    方法 findSystemClass


    findSystemClass 方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用 defineClass 将原始字节转换成 Class 对象,以将该文件转换成类。当运行 Java 应用程序时,这是 JVM 正常装入类的缺省机制。(Java 2 中 ClassLoader 的变动提供了关于 Java 版本 1.2 这个过程变动的详细信息。)

    对于定制的 ClassLoader,只有在尝试其它方法装入类之后,再使用 findSystemClass。原因很简单:ClassLoader 是负责执行装入类的特殊步骤,不是负责所有类。例如,即使 ClassLoader 从远程的 Web 站点装入了某些类,仍然需要在本地机器上装入大量的基本 Java 库。而这些类不是我们所关心的,所以要 JVM 以缺省方式装入它们:从本地文件系统。这就是 findSystemClass 的用途。

    其工作流程如下:


    请求定制的 ClassLoader 装入类。 
    检查远程 Web 站点,查看是否有所需要的类。 
    如果有,那么好;抓取这个类,完成任务。 
    如果没有,假定这个类是在基本 Java 库中,那么调用 findSystemClass,使它从文件系统装入该类。 
    在大多数定制 ClassLoaders 中,首先调用 findSystemClass 以节省在本地就可以装入的许多 Java 库类而要在远程 Web 站点上查找所花的时间。然而,正如,在下一章节所看到的,直到确信能自动编译我们的应用程序代码时,才让 JVM 从本地文件系统装入类。

     


    方法 resolveClass

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

    方法 findLoadedClass

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


    三.命名空间及其作用

    每个类装载器有自己的命名空间,命名空间由所有以此装载器为创始类装载器的类组成。不同命名空间的两个类是不可见的,但只要得到类所对应的Class对象的reference,还是可以访问另一命名空间的类。

     

    例2演示了一个命名空间的类如何使用另一命名空间的类。在例子中,LoaderSample2由系统类装载器装载,LoaderSample3由自定义的装载器loader负责装载,两个类不在同一命名空间,但LoaderSample2得到了LoaderSample3所对应的Class对象的reference,所以它可以访问LoaderSampl3中公共的成员(如age)。

    例2不同命名空间的类的访问

    /*LoaderSample2.java*/

    import  java.net. * ;

    import  java.lang.reflect. * ;

    public   class  LoaderSample2 {

         public   static   void  main(String[] args) {

             try  {

                String path  =  System.getProperty( " user.dir " );

                URL[] us  =  { new  URL( " file:// "   +  path  +   " /sub/ " )};

                ClassLoader loader  =   new  URLClassLoader(us);

                Class c  =  loader.loadClass( " LoaderSample3 " );

                Object o  =  c.newInstance();

                Field f  =  c.getField( " age " );

                 int  age  =  f.getInt(o);

                System.out.println( " age is  "   +  age);

            }  catch  (Exception e) {

                e.printStackTrace();

            }

        }

    }


    /*sub/Loadersample3.java*/

    public   class  LoaderSample3 {

         static  {

            System.out.println( " LoaderSample3 loaded " );

        }

         public   int  age  =   30 ;

    }

    编译:javac LoaderSample2.java; javac sub/LoaderSample3.java

    运行:java LoaderSample2

    LoaderSample3 loaded

    age is 30

    从运行结果中可以看出,在类LoaderSample2中可以创建处于另一命名空间的类LoaderSample3中的对象并可以访问其公共成员age。

    运行时包(runtime package)

    由同一类装载器定义装载的属于相同包的类组成了运行时包,决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看的定义类装载器是否相同。只有属于同一运行时包的类才能互相访问包可见的类和成员。这样的限制避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。假设用户自己定义了一个类java.lang.Yes,并用用户自定义的类装载器装载,由于java.lang.Yes和核心类库java.lang.*由不同的装载器装载,它们属于不同的运行时包,所以java.lang.Yes不能访问核心类库java.lang中类的包可见的成员。


    (7)有关ClassLoader的重载
      扩展ClassLoader方法

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

         public  Class findClass(String name)    {

             byte [] data  =  loadClassData(name);

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

        }


       我们还应该提供一个方法loadClassData(String name),通过类的名称返回class文件的字

    节数组。然后使用ClassLoader提供的defineClass()方法我们就可以返回Class对象了。

         public   byte [] loadClassData(String name)    {

            FileInputStream fis  =   null ;

             byte [] data  =   null ;

             try  {

                fis  =   new  FileInputStream( new  File(drive  +  name  +  fileType));

                ByteArrayOutputStream baos  =   new  ByteArrayOutputStream();

                 int  ch  =   0 ;

                 while  ((ch  =  fis.read())  !=   - 1 )  {

                    baos.write(ch);              

                }

                data  =  baos.toByteArray();

            }  catch  (IOException e)  {

                e.printStackTrace();

            }       

             return  data;

        }

    展开全文
  • 深入理解Classloader

    2011-03-14 13:59:53
    ClassLoader 当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构: bootstrap classloader | extension classloader | system classloader bootstrap class...
    ClassLoader
    当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构:

    bootstrap classloader
    |
    extension classloader
    |
    system classloader

    bootstrap classloader -引导(也称为原始)类加载器,它负责加载Java的核心类。在Sun的JVM中,在执行java的命令中使用-Xbootclasspath选项或使用- D选项指定sun.boot.class.path系统属性值可以指定附加的类。这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的。大家可以通过执行以下代码来获得bootstrap classloader加载了那些核心类库:
    URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
    for (int i = 0; i < urls.length; i++) {
    System.out.println(urls.toExternalform());
    }
    在我的计算机上的结果为:
    文件:/C:/j2sdk1.4.1_01/jre/lib/endorsed/dom.jar
    文件:/C:/j2sdk1.4.1_01/jre/lib/endorsed/sax.jar
    文件:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xalan-2.3.1.jar
    文件:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xercesImpl-2.0.0.jar
    文件:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xml-apis.jar
    文件:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xsltc.jar
    文件:/C:/j2sdk1.4.1_01/jre/lib/rt.jar
    文件:/C:/j2sdk1.4.1_01/jre/lib/i18n.jar
    文件:/C:/j2sdk1.4.1_01/jre/lib/sunrsasign.jar
    文件:/C:/j2sdk1.4.1_01/jre/lib/jsse.jar
    文件:/C:/j2sdk1.4.1_01/jre/lib/jce.jar
    文件:/C:/j2sdk1.4.1_01/jre/lib/charsets.jar
    文件:/C:/j2sdk1.4.1_01/jre/classes
    这时大家知道了为什么我们不需要在系统属性CLASSPATH中指定这些类库了吧,因为JVM在启动的时候就自动加载它们了。

    extension classloader -扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类包。这为引入除Java核心类以外的新功能提供了一个标准机制。因为默认的扩展目录对所有从同一个JRE中启动的JVM都是通用的,所以放入这个目录的 JAR类包对所有的JVM和system classloader都是可见的。在这个实例上调用方法getParent()总是返回空值null,因为引导加载器bootstrap classloader不是一个真正的ClassLoader实例。所以当大家执行以下代码时:
    System.out.println(System.getProperty("java.ext.dirs"));
    ClassLoader extensionClassloader=ClassLoader.getSystemClassLoader().getParent();
    System.out.println("the parent of extension classloader : "+extensionClassloader.getParent());
    结果为:
    C:\j2sdk1.4.1_01\jre\lib\ext
    the parent of extension classloader : null
    extension classloader是system classloader的parent,而bootstrap classloader是extension classloader的parent,但它不是一个实际的classloader,所以为null。

    system classloader -系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径。总能通过静态方法ClassLoader.getSystemClassLoader()找到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。执行以下代码即可获得:
    System.out.println(System.getProperty("java.class.path"));
    输出结果则为用户在系统属性里面设置的CLASSPATH。
    classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入;委托机制则是先让parent(父)类加载器 (而不是super,它与parent classloader类不是继承关系)寻找,只有在parent找不到的时候才从自己的类路径中去寻找。此外类加载还采用了cache机制,也就是如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必须重新启动JVM才能生效的原因。

    每个ClassLoader加载Class的过程是:
    1.检测此Class是否载入过(即在cache中是否有此Class),如果有到8,如果没有到2
    2.如果parent classloader不存在(没有parent,那parent一定是bootstrap classloader了),到4
    3.请求parent classloader载入,如果成功到8,不成功到5
    4.请求jvm从bootstrap classloader中载入,如果成功到8
    5.寻找Class文件(从与此classloader相关的类路径中寻找)。如果找不到则到7.
    6.从文件中载入Class,到8.
    7.抛出ClassNotFoundException.
    8.返回Class.

    其中5.6步我们可以通过覆盖ClassLoader的findClass方法来实现自己的载入策略。甚至覆盖loadClass方法来实现自己的载入过程。

    类加载器的顺序是:
    先是bootstrap classloader,然后是extension classloader,最后才是system classloader。大家会发现加载的Class越是重要的越在靠前面。这样做的原因是出于安全性的考虑,试想如果system classloader“亲自”加载了一个具有破坏性的“java.lang.System”类的后果吧。这种委托机制保证了用户即使具有一个这样的类,也把它加入到了类路径中,但是它永远不会被载入,因为这个类总是由bootstrap classloader来加载的。大家可以执行一下以下的代码:
    System.out.println(System.class.getClassLoader());
    将会看到结果是null,这就表明java.lang.System是由bootstrap classloader加载的,因为bootstrap classloader不是一个真正的ClassLoader实例,而是由JVM实现的,正如前面已经说过的。

    下面就让我们来看看JVM是如何来为我们来建立类加载器的结构的:
    sun.misc.Launcher,顾名思义,当你执行java命令的时候,JVM会先使用bootstrap classloader载入并初始化一个Launcher,执行下来代码:
    System.out.println("the Launcher's classloader is "+sun.misc.Launcher.getLauncher().getClass().getClassLoader());
    结果为:
    the Launcher's classloader is null (因为是用bootstrap classloader加载,所以class loader为null)
    Launcher 会根据系统和命令设定初始化好class loader结构,JVM就用它来获得extension classloader和system classloader,并载入所有的需要载入的Class,最后执行java命令指定的带有静态的main方法的Class。extension classloader实际上是sun.misc.Launcher$ExtClassLoader类的一个实例,system classloader实际上是sun.misc.Launcher$AppClassLoader类的一个实例。并且都是 java.net.URLClassLoader的子类。

    让我们来看看Launcher初试化的过程的部分代码。

    Launcher的部分代码:
    public class Launcher {
    public Launcher() {
    ExtClassLoader extclassloader;
    try {
    //初始化extension classloader
    extclassloader = ExtClassLoader.getExtClassLoader();
    } catch(IOException ioexception) {
    throw new InternalError("Could not create extension class loader");
    }
    try {
    //初始化system classloader,parent是extension classloader
    loader = AppClassLoader.getAppClassLoader(extclassloader);
    } catch(IOException ioexception1) {
    throw new InternalError("Could not create application class loader");
    }
    //将system classloader设置成当前线程的context classloader(将在后面加以介绍)
    Thread.currentThread().setContextClassLoader(loader);
    ......
    }
    public ClassLoader getClassLoader() {
    //返回system classloader
    return loader;
    }
    }

    extension classloader的部分代码:
    static class Launcher$ExtClassLoader extends URLClassLoader {

    public static Launcher$ExtClassLoader getExtClassLoader()
    throws IOException
    {
    File afile[] = getExtDirs();
    return (Launcher$ExtClassLoader)AccessController.doPrivileged(new Launcher$1(afile));
    }
    private static File[] getExtDirs() {
    //获得系统属性“java.ext.dirs”
    String s = System.getProperty("java.ext.dirs");
    File afile[];
    if(s != null) {
    StringTokenizer stringtokenizer = new StringTokenizer(s, File.pathSeparator);
    int i = stringtokenizer.countTokens();
    afile = new File;
    for(int j = 0; j < i; j++)
    afile[j] = new File(stringtokenizer.nextToken());

    } else {
    afile = new File[0];
    }
    return afile;
    }
    }

    system classloader的部分代码:
    static class Launcher$AppClassLoader extends URLClassLoader
    {
    public static ClassLoader getAppClassLoader(ClassLoader classloader)
    throws IOException
    {
    //获得系统属性“java.class.path”
    String s = System.getProperty("java.class.path");
    File afile[] = s != null ? Launcher.access$200(s) : new File[0];
    return (Launcher$AppClassLoader)AccessController.doPrivileged(new Launcher$2(s, afile, classloader));
    }
    }

    看了源代码大家就清楚了吧,extension classloader是使用系统属性“java.ext.dirs”设置类搜索路径的,并且没有parent。system classloader是使用系统属性“java.class.path”设置类搜索路径的,并且有一个parent classloader。Launcher初始化extension classloader,system classloader,并将system classloader设置成为context classloader,但是仅仅返回system classloader给JVM。

      这里怎么又出来一个context classloader呢?它有什么用呢?我们在建立一个线程Thread的时候,可以为这个线程通过setContextClassLoader方法来指定一个合适的classloader作为这个线程的context classloader,当此线程运行的时候,我们可以通过getContextClassLoader方法来获得此context classloader,就可以用它来载入我们所需要的Class。默认的是system classloader。利用这个特性,我们可以“打破”classloader委托机制了,父classloader可以获得当前线程的context classloader,而这个context classloader可以是它的子classloader或者其他的classloader,那么父classloader就可以从其获得所需的 Class,这就打破了只能向父classloader请求的限制了。这个机制可以满足当我们的classpath是在运行时才确定,并由定制的 classloader加载的时候,由system classloader(即在jvm classpath中)加载的class可以通过context classloader获得定制的classloader并加载入特定的class(通常是抽象类和接口,定制的classloader中是其实现),例如web应用中的servlet就是用这种机制加载的.


    好了,现在我们了解了classloader的结构和工作原理,那么我们如何实现在运行时的动态载入和更新呢?只要我们能够动态改变类搜索路径和清除classloader的cache中已经载入的Class就行了,有两个方案,一是我们继承一个classloader,覆盖loadclass方法,动态的寻找Class文件并使用defineClass方法来;另一个则非常简单实用,只要重新使用一个新的类搜索路径来new一个classloader就行了,这样即更新了类搜索路径以便来载入新的Class,也重新生成了一个空白的cache(当然,类搜索路径不一定必须更改)。噢,太好了,我们几乎不用做什么工作,java.netURLClassLoader正是一个符合我们要求的classloader!我们可以直接使用或者继承它就可以了!

    这是j2se1.4 API的doc中URLClassLoader的两个构造器的描述:
    URLClassLoader(URL[] urls)
    Constructs a new URLClassLoader for the specified URLs using the default delegation parent ClassLoader.
    URLClassLoader(URL[] urls, ClassLoader parent)
    Constructs a new URLClassLoader for the given URLs.
    其中URL[] urls就是我们要设置的类搜索路径,parent就是这个classloader的parent classloader,默认的是system classloader。


    好,现在我们能够动态的载入Class了,这样我们就可以利用newInstance方法来获得一个Object。但我们如何将此Object造型呢?可以将此Object造型成它本身的Class吗?

    首先让我们来分析一下java源文件的编译,运行吧!javac命令是调用“JAVA_HOME/lib/tools.jar”中的“com.sun.tools.javac.Main”的compile方法来编译:

    public static int compile(String as[]);

    public static int compile(String as[], PrintWriter printwriter);

    返回0表示编译成功,字符串数组as则是我们用javac命令编译时的参数,以空格划分。例如:
    javac -classpath c:\foo\bar.jar;. -d c:\ c:\Some.java
    则字符串数组as为{"-classpath","c:\\foo\\bar.jar;.","-d","c:\\","c:\\Some.java"},如果带有PrintWriter参数,则会把编译信息出到这个指定的printWriter中。默认的输出是System.err。

    其中 Main是由JVM使用Launcher初始化的system classloader载入的,根据全盘负责原则,编译器在解析这个java源文件时所发现的它所依赖和引用的所有Class也将由system classloader载入,如果system classloader不能载入某个Class时,编译器将抛出一个“cannot resolve symbol”错误。

    所以首先编译就通不过,也就是编译器无法编译一个引用了不在CLASSPATH中的未知Class的java源文件,而由于拼写错误或者没有把所需类库放到CLASSPATH中,大家一定经常看到这个“cannot resolve symbol”这个编译错误吧!

    其次,就是我们把这个Class放到编译路径中,成功的进行了编译,然后在运行的时候不把它放入到CLASSPATH中而利用我们自己的 classloader来动态载入这个Class,这时候也会出现“java.lang.NoClassDefFoundError”的违例,为什么呢?

    我们再来分析一下,首先调用这个造型语句的可执行的Class一定是由JVM使用Launcher初始化的system classloader载入的,根据全盘负责原则,当我们进行造型的时候,JVM也会使用system classloader来尝试载入这个Class来对实例进行造型,自然在system classloader寻找不到这个Class时就会抛出“java.lang.NoClassDefFoundError”的违例。

    OK,现在让我们来总结一下,java文件的编译和Class的载入执行,都是使用Launcher初始化的system classloader作为类载入器的,我们无法动态的改变system classloader,更无法让JVM使用我们自己的classloader来替换system classloader,根据全盘负责原则,就限制了编译和运行时,我们无法直接显式的使用一个system classloader寻找不到的Class,即我们只能使用Java核心类库,扩展类库和CLASSPATH中的类库中的Class。

    还不死心!再尝试一下这种情况,我们把这个Class也放入到CLASSPATH中,让system classloader能够识别和载入。然后我们通过自己的classloader来从指定的class文件中载入这个Class(不能够委托 parent载入,因为这样会被system classloader从CLASSPATH中将其载入),然后实例化一个Object,并造型成这个Class,这样JVM也识别这个Class(因为 system classloader能够定位和载入这个Class从CLASSPATH中),载入的也不是CLASSPATH中的这个Class,而是从 CLASSPATH外动态载入的,这样总行了吧!十分不幸的是,这时会出现“java.lang.ClassCastException”违例。

    为什么呢?我们也来分析一下,不错,我们虽然从CLASSPATH外使用我们自己的classloader动态载入了这个Class,但将它的实例造型的时候是JVM会使用system classloader来再次载入这个Class,并尝试将使用我们的自己的classloader载入的Class的一个实例造型为system classloader载入的这个Class(另外的一个)。大家发现什么问题了吗?也就是我们尝试将从一个classloader载入的Class的一个实例造型为另外一个classloader载入的Class,虽然这两个Class的名字一样,甚至是从同一个class文件中载入。但不幸的是JVM 却认为这个两个Class是不同的,即JVM认为不同的classloader载入的相同的名字的Class(即使是从同一个class文件中载入的)是不同的!这样做的原因我想大概也是主要出于安全性考虑,这样就保证所有的核心Java类都是system classloader载入的,我们无法用自己的classloader载入的相同名字的Class的实例来替换它们的实例。

    看到这里,聪明的读者一定想到了该如何动态载入我们的Class,实例化,造型并调用了吧!

    那就是利用面向对象的基本特性之一的多形性。我们把我们动态载入的Class的实例造型成它的一个system classloader所能识别的父类就行了!这是为什么呢?我们还是要再来分析一次。当我们用我们自己的classloader来动态载入这我们只要把这个Class的时候,发现它有一个父类Class,在载入它之前JVM先会载入这个父类Class,这个父类Class是system classloader所能识别的,根据委托机制,它将由system classloader载入,然后我们的classloader再载入这个Class,创建一个实例,造型为这个父类Class,注意了,造型成这个父类 Class的时候(也就是上溯)是面向对象的java语言所允许的并且JVM也支持的,JVM就使用system classloader再次载入这个父类Class,然后将此实例造型为这个父类Class。大家可以从这个过程发现这个父类Class都是由 system classloader载入的,也就是同一个class loader载入的同一个Class,所以造型的时候不会出现任何异常。而根据多形性,调用这个父类的方法时,真正执行的是这个Class(非父类 Class)的覆盖了父类方法的方法。这些方法中也可以引用system classloader不能识别的Class,因为根据全盘负责原则,只要载入这个Class的classloader即我们自己定义的 classloader能够定位和载入这些Class就行了。

    这样我们就可以事先定义好一组接口或者基类并放入CLASSPATH中,然后在执行的时候动态的载入实现或者继承了这些接口或基类的子类。还不明白吗?让我们来想一想Servlet吧,web application server能够载入任何继承了Servlet的Class并正确的执行它们,不管它实际的Class是什么,就是都把它们实例化成为一个Servlet Class,然后执行Servlet的init,doPost,doGet和destroy等方法的,而不管这个Servlet是从web- inf/lib和web-inf/classes下由system classloader的子classloader(即定制的classloader)动态载入。说了这么多希望大家都明白了。在applet,ejb等容器中,都是采用了这种机制.

    对于以上各种情况,希望大家实际编写一些example来实验一下。

    最后我再说点别的, classloader虽然称为类加载器,但并不意味着只能用来加载Class,我们还可以利用它也获得图片,音频文件等资源的URL,当然,这些资源必须在CLASSPATH中的jar类库中或目录下。我们来看API的doc中关于ClassLoader的两个寻找资源和Class的方法描述吧:
            public URL getResource(String name)
            用指定的名字来查找资源,一个资源是一些能够被class代码访问的在某种程度上依赖于代码位置的数据(图片,音频,文本等等)。
    一个资源的名字是以'/'号分隔确定资源的路径名的。
    这个方法将先请求parent classloader搜索资源,如果没有parent,则会在内置在虚拟机中的classloader(即bootstrap classloader)的路径中搜索。如果失败,这个方法将调用findResource(String)来寻找资源。
            public static URL getSystemResource(String name)
    从用来载入类的搜索路径中查找一个指定名字的资源。这个方法使用system class loader来定位资源。即相当于ClassLoader.getSystemClassLoader().getResource(name)。

    例如:
    System.out.println(ClassLoader.getSystemResource("java/lang/String.class"));
    的结果为:
    jar:文件:/C:/j2sdk1.4.1_01/jre/lib/rt.jar!/java/lang/String.class
    表明String.class文件在rt.jar的java/lang目录中。
    因此我们可以将图片等资源随同Class一同打包到jar类库中(当然,也可单独打包这些资源)并添加它们到class loader的搜索路径中,我们就可以无需关心这些资源的具体位置,让class loader来帮我们寻找了!
    RTTI
    2007-09-04
    Java运行时类型识别RTTI
    关键字: java rtti
    运行时类型识别(run-time type identification ,RTTI)的概念上看非常简单:当只有一个指向对象基类的引用时RTTI机制可以让你找到这个对象的确切概念。

    1。Class对象是RTTI的核心,Class的类的类,每个类都有一个class对象。每当编写并且编译一个新类,就会产生一个Class对象(被保存在同名的.class文件当中)

    2。Class.forName("classname"),如果对象没有加载就加载对象(这将会触发类的静态初始化)
    Class.newInstance()用来产生一个对象。如
    Class m = Class.forName("classname");//1
    Object o = m.newInstance();//2
    java也提供"类字面常量"的机制生成对象的引用。像这样:
    A.class
    对于基本类型,boolean.class === Boolean.TYPE , char.class ===Character.TYP
    void.class ===Void.TYPE,等等。。。。
    那么也可以用Class m = char.class; //或者 Class m = <aclass>.class
    Object o = m.newInstance();
    ((Char)o).××

    3。instanceof 关键字用于检查对象是不是某个特定类型的实例。这用于类型转换前做检测。如:
    if ( x instanceof Dog )
    ((Dog)x).bark();
    除了 instanceof 关键字以外,还可以使用 Class.isInstance() 方法,两者功能相同。

    4。instanceof的替代方案是: x.getClass == Y.class 或者x.getClass.equals( Y.class)

    5。Class对象的getInterfaces()获得接口,getSurperClass 或者获得超类。

    6。反射是运行时的类信息。java附带的库java.lang.reflect含有Field,Method,Constructor类(每个类都实现了Memeber接口)。这些类型的对象是有JVM在运行时创建的,用以表示未知类里对象的成员,然后用Constructor创建新的对象,用get ()和set()方法读取和修改Field对象关联的字段,用invoke()方法调用于Method对象关联的方 法,还可以用getFields(),getMethods(),getConstructors()等等方法。

    序列化
    【IT168 编程开发】目前网络上关于对象序列化的文章不少,但是我发现详细叙述用法和原理的文章太少。本人把自己经过经验总结和实际运用中的体会写成的学习笔记贡献给大家。希望能为整个java社区的繁荣做一点事情。
    序列化的过程就是对象写入字节流和从字节流中读取对象。将对象状态转换成字节流之后,可以用java.io包中的各种字节流类将其保存到文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机。对象序列化功能非常简单、强大,在RMI、Socket、JMS、EJB都有应用。对象序列化问题在网络编程中并不是最激动人心的课题,但却相当重要,具有许多实用意义。
    一:对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。
    二:java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。
    从上面的叙述中,我们知道了对象序列化是java编程中的必备武器,那么让我们从基础开始,好好学习一下它的机制和用法。
    java序列化比较简单,通常不需要编写保存和恢复对象状态的定制代码。实现java.io.Serializable接口的类对象可以转换成字节流或从字节流恢复,不需要在类中增加任何代码。只有极少数情况下才需要定制代码保存或恢复对象状态。这里要注意:不是每个类都可序列化,有些类是不能序列化的,例如涉及线程的类与特定JVM有非常复杂的关系。
    序列化机制:
    序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。ObjectOutputStream中的序列化过程与字节流连接,包括对象类型和版本信息。反序列化时,JVM用头信息生成对象实例,然后将对象字节流中的数据复制到对象数据成员中。下面我们分两大部分来阐述:
    处理对象流:
    (序列化过程和反序列化过程)
    java.io包有两个序列化对象的类。ObjectOutputStream负责将对象写入字节流,ObjectInputStream从字节流重构对象。
    我们先了解ObjectOutputStream类吧。ObjectOutputStream类扩展DataOutput接口。
    writeObject()方法是最重要的方法,用于对象序列化。如果对象包含其他对象的引用,则writeObject()方法递归序列化这些对象。每个ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。(这点很重要)由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。
    下面,让我们从例子中来了解ObjectOutputStream这个类吧。
    // 序列化 today's date 到一个文件中.
    FileOutputStream f = new FileOutputStream("tmp");
    ObjectOutputStream s = new ObjectOutputStream(f);
    s.writeObject("Today");
    s.writeObject(new Date());
    s.flush();
    现在,让我们来了解ObjectInputStream这个类。它与ObjectOutputStream相似。它扩展DataInput接口。ObjectInputStream中的方法镜像DataInputStream中读取Java基本数据类型的公开方法。readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。
    例子如下:
    //从文件中反序列化 string 对象和 date 对象
    FileInputStream in = new FileInputStream("tmp");
    ObjectInputStream s = new ObjectInputStream(in);
    String today = (String)s.readObject();
    Date date = (Date)s.readObject();
    定制序列化过程:
    序列化通常可以自动完成,但有时可能要对这个过程进行控制。java可以将类声明为serializable,但仍可手工控制声明为static或transient的数据成员。
    例子:一个非常简单的序列化类。
    public class simpleSerializableClass implements Serializable{
    String sToday="Today:";
    transient Date dtToday=new Date();
    }
    序列化时,类的所有数据成员应可序列化除了声明为transient或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient。我们的类要用writeObject()与readObject()方法以处理这些数据成员。使用writeObject()与readObject()方法时,还要注意按写入的顺序读取这些数据成员。
    关于如何使用定制序列化的部分代码如下:
    //重写writeObject()方法以便处理transient的成员。
    public void writeObject(ObjectOutputStream outputStream) throws IOException{
    outputStream.defaultWriteObject();//使定制的writeObject()方法可以
    利用自动序列化中内置的逻辑。
    outputStream.writeObject(oSocket.getInetAddress());
    outputStream.writeInt(oSocket.getPort());
    }
    //重写readObject()方法以便接收transient的成员。
    private void readObject(ObjectInputStream inputStream) throws IOException,ClassNotFoundException{
    inputStream.defaultReadObject();//defaultReadObject()补充自动序列化
    InetAddress oAddress=(InetAddress)inputStream.readObject();
    int iPort =inputStream.readInt();
    oSocket = new Socket(oAddress,iPort);
    iID=getID();
    dtToday =new Date();
    }
    完全定制序列化过程:
    如果一个类要完全负责自己的序列化,则实现Externalizable接口而不是Serializable接口。Externalizable接口定义包括两个方法writeExternal()与readExternal()。利用这些方法可以控制对象数据成员如何写入字节流.类实现Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了。声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。到此为至,我们学习了序列化的基础部分知识。关于序
    列化的高级教程,以后再述。
    新一篇: Java-RTTI与反射机制--详细
    Java提供了一套机制来动态执行方法和构造方法,以及数组操作等,这套机制就叫——反射。反射机制是如今很多流行框架的实现基础,其中包括Spring、Hibernate等。原理性的问题不是本文的重点,接下来让我们在实例中学习这套精彩的机制。

    1. 得到某个对象的属性
    1 public Object getProperty(Object owner, String fieldName) throws Exception {
    2 Class ownerClass = owner.getClass();
    3
    4 Field field = ownerClass.getField(fieldName);
    5
    6 Object property = field.get(owner);
    7
    8 return property;
    9 }


    Class ownerClass = owner.getClass():得到该对象的Class。

    Field field = ownerClass.getField(fieldName):通过Class得到类声明的属性。

    Object property = field.get(owner):通过对象得到该属性的实例,如果这个属性是非公有的,这里会报IllegalAccessException。


    2. 得到某个类的静态属性
    1 public Object getStaticProperty(String className, String fieldName)
    2 throws Exception {
    3 Class ownerClass = Class.forName(className);
    4
    5 Field field = ownerClass.getField(fieldName);
    6
    7 Object property = field.get(ownerClass);
    8
    9 return property;
    10 }


    Class ownerClass = Class.forName(className) :首先得到这个类的Class。

    Field field = ownerClass.getField(fieldName):和上面一样,通过Class得到类声明的属性。

    Object property = field.get(ownerClass) :这里和上面有些不同,因为该属性是静态的,所以直接从类的Class里取。


    3. 执行某对象的方法
    1 public Object invokeMethod(Object owner, String methodName, Object[] args) throws Exception {
    2
    3 Class ownerClass = owner.getClass();
    4
    5 Class[] argsClass = new Class[args.length];
    6
    7 for (int i = 0, j = args.length; i < j; i++) {
    8 argsClass[i] = args[i].getClass();
    9 }
    10
    11 Method method = ownerClass.getMethod(methodName, argsClass);
    12
    13 return method.invoke(owner, args);
    14 }

    Class owner_class = owner.getClass() :首先还是必须得到这个对象的Class。

    5~9行:配置参数的Class数组,作为寻找Method的条件。

    Method method = ownerClass.getMethod(methodName, argsClass):通过Method名和参数的Class数组得到要执行的Method。

    method.invoke(owner, args):执行该Method,invoke方法的参数是执行这个方法的对象,和参数数组。返回值是Object,也既是该方法的返回值。


    4. 执行某个类的静态方法
    1 public Object invokeStaticMethod(String className, String methodName,
    2 Object[] args) throws Exception {
    3 Class ownerClass = Class.forName(className);
    4
    5 Class[] argsClass = new Class[args.length];
    6
    7 for (int i = 0, j = args.length; i < j; i++) {
    8 argsClass[i] = args[i].getClass();
    9 }
    10
    11 Method method = ownerClass.getMethod(methodName, argsClass);
    12
    13 return method.invoke(null, args);
    14 }


    基本的原理和实例3相同,不同点是最后一行,invoke的一个参数是null,因为这是静态方法,不需要借助实例运行。


    5. 新建实例
    1
    2 public Object newInstance(String className, Object[] args) throws Exception {
    3 Class newoneClass = Class.forName(className);
    4
    5 Class[] argsClass = new Class[args.length];
    6
    7 for (int i = 0, j = args.length; i < j; i++) {
    8 argsClass[i] = args[i].getClass();
    9 }
    10
    11 Constructor cons = newoneClass.getConstructor(argsClass);
    12
    13 return cons.newInstance(args);
    14
    15 }


    这里说的方法是执行带参数的构造函数来新建实例的方法。如果不需要参数,可以直接使用newoneClass.newInstance()来实现。

    Class newoneClass = Class.forName(className):第一步,得到要构造的实例的Class。

    第5~第9行:得到参数的Class数组。

    Constructor cons = newoneClass.getConstructor(argsClass):得到构造子。

    cons.newInstance(args):新建实例。


    6. 判断是否为某个类的实例
    1 public boolean isInstance(Object obj, Class cls) {
    2 return cls.isInstance(obj);
    3 }


    7. 得到数组中的某个元素
    1 public Object getByArray(Object array, int index) {
    2 return Array.get(array,index);
    3 }


    附完整源码:
    import java.lang.reflect.Array;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;


    /**
    * Java Reflection Cookbook
    *
    * @author Michael Lee
    * @since 2006-8-23
    * @version 0.1a
    */

    public class Reflection {
    /**
    * 得到某个对象的公共属性
    *
    * @param owner, fieldName
    * @return 该属性对象
    * @throws Exception
    *
    */
    public Object getProperty(Object owner, String fieldName) throws Exception {
    Class ownerClass = owner.getClass();

    Field field = ownerClass.getField(fieldName);

    Object property = field.get(owner);

    return property;
    }

    /**
    * 得到某类的静态公共属性
    *
    * @param className 类名
    * @param fieldName 属性名
    * @return 该属性对象
    * @throws Exception
    */
    public Object getStaticProperty(String className, String fieldName)
    throws Exception {
    Class ownerClass = Class.forName(className);

    Field field = ownerClass.getField(fieldName);

    Object property = field.get(ownerClass);

    return property;
    }


    /**
    * 执行某对象方法
    *
    * @param owner
    * 对象
    * @param methodName
    * 方法名
    * @param args
    * 参数
    * @return 方法返回值
    * @throws Exception
    */
    public Object invokeMethod(Object owner, String methodName, Object[] args)
    throws Exception {

    Class ownerClass = owner.getClass();

    Class[] argsClass = new Class[args.length];

    for (int i = 0, j = args.length; i < j; i++) {
    argsClass[i] = args[i].getClass();
    }

    Method method = ownerClass.getMethod(methodName, argsClass);

    return method.invoke(owner, args);
    }


    /**
    * 执行某类的静态方法
    *
    * @param className
    * 类名
    * @param methodName
    * 方法名
    * @param args
    * 参数数组
    * @return 执行方法返回的结果
    * @throws Exception
    */
    public Object invokeStaticMethod(String className, String methodName,
    Object[] args) throws Exception {
    Class ownerClass = Class.forName(className);

    Class[] argsClass = new Class[args.length];

    for (int i = 0, j = args.length; i < j; i++) {
    argsClass[i] = args[i].getClass();
    }

    Method method = ownerClass.getMethod(methodName, argsClass);

    return method.invoke(null, args);
    }


    /**
    * 新建实例
    *
    * @param className
    * 类名
    * @param args
    * 构造函数的参数
    * @return 新建的实例
    * @throws Exception
    */
    public Object newInstance(String className, Object[] args) throws Exception {
    Class newoneClass = Class.forName(className);

    Class[] argsClass = new Class[args.length];

    for (int i = 0, j = args.length; i < j; i++) {
    argsClass[i] = args[i].getClass();
    }

    Constructor cons = newoneClass.getConstructor(argsClass);

    return cons.newInstance(args);

    }



    /**
    * 是不是某个类的实例
    * @param obj 实例
    * @param cls 类
    * @return 如果 obj 是此类的实例,则返回 true
    */
    public boolean isInstance(Object obj, Class cls) {
    return cls.isInstance(obj);
    }

    /**
    * 得到数组中的某个元素
    * @param array 数组
    * @param index 索引
    * @return 返回指定数组对象中索引组件的值
    */
    public Object getByArray(Object array, int index) {
    return Array.get(array,index);
    }
    }
    展开全文
  • 深入理解classloader

    2012-10-10 16:58:07
    classloader加载类的方法:ClassLoader.loadClass(String className) 如下例子: public class User { private String name; private Integer age; public User(String name, Integer age) { super(); this....
  • 第05讲 深入理解 ClassLoader 的加载机制 拉勾教育:https://kaiwu.lagou.com/course/courseInfo.htm 这一讲主要讲了class文件的加载时机,及加载class的双亲委派机制。还举例说明了如何自定义classLoader。最后还...
  • 深入理解ClassLoader工作机制

    千次阅读 2019-05-05 20:30:50
    ClassLoader类加载器可以说是Java中必学内容之一,无论是想要去研究Concurrent包、Unsafe,还是深入学习Spark等分布式计算框架,都必须对此有一定的理解。笔者在写之前也只了解了皮毛,想通过这篇文章,结合一些书籍...
  • JVM内存模型,类加载模式工作机制详细,内存屏障,类从被加载到虚拟机内存中开始,直到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析这三...
  • 深入理解ClassLoader工作机制(jdk1.8)

    万次阅读 多人点赞 2018-08-05 23:28:46
    ClassLoader 顾名思义就是类加载器,ClassLoader 作用: 负责将 Class 加载到 JVM 中 审查每个类由谁加载(父优先的等级加载机制) 将 Class 字节码重新解析成 JVM 统一要求的对象格式 类加载时机与过程 类从被...
  • 一、什么是ClassLoader? ClassLoader 顾名思义就是类加载器,ClassLoader 作用: 负责将 Class 加载到 JVM 中 审查每个类由谁加载(父优先的等级加载机制) 将 Class 字节码重新解析成 JVM 统一要求的对象格式 ...
  • 今天开始复习JAVA的相关知识,又碰到了ClassLoader问题,所以拿来好好回顾一番。  基本功能   ClassLoader的主要作用是对类的请求提供服务,当JVM需要某类时,它根据名称向ClassLoader要求这个类,然后由...
  • 文件类加载器,该加载器重载了loadClass方法,逻辑是只读取...package com.ydd.study.hello.classloader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; imp...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 637
精华内容 254
关键字:

深入理解classloader