8-ClassLoader

来源:互联网 发布:java接口对于耦合实例 编辑:程序博客网 时间:2024/06/14 12:36

ClassLoader的核心作用是:加载指定路径的class

JVM将所需要运行的*.class文件加载到JVM进程中需要有一个类加载器(ClassLoader,系统会提供默认类加载器,我们也可以自己写),类加载器的好处就在于我们可以随意指定*.class文件的位置(包括网络上的)

类的初始化时间

调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

示例:

class TestClass{         static {                  System.out.println("TestClass");         }} public class Test2 {         public static voidmain(String[] args) throws ClassNotFoundException {                  ClassLoader classLoader= ClassLoader.getSystemClassLoader();                  Class<?> clazz= classLoader.loadClass("edu.swust.JVM.classloader.TestClass");                  // 只是加载,没有初始化                  System.out.println("-----------------------");                  clazz = Class.forName("edu.swust.JVM.classloader.TestClass");        // 初始化                  /**                   * 结果                   * -----------------------         * TestClass         * -----------------------         * classedu.swust.JVM.classloader.TestClass                   */         }}


 

 

双亲委托机制

类加载器用来把类加载到java虚拟机中,从JDK1.2开始,类加载过程采用了父亲委托机制,这种机制更好的保证了java平台的安全。在这个委托机制中,除了java虚拟机自带的根类加载器以外,其余的类加载器都有且只有一个父加载器。当加载器请求加载器loader1加载Sample类时,loader1首先委托自己的父加载器去加载Sample类,若父加载器能够加载,则由父加载器完成加载任务,否则才由加载器loader1本身加载Sample类。

 

若有一个类加载器能够成功加载Sample类,那么这个类加载器称为定义类加载器,所有能够成功返回class对象的引用的类加载器(包括定义类加载器和它的子加载器)都称为初始类加载器

 

类加载器分两种

         Java虚拟机自带的加载器

                  BootstrapClassLoader根加载器(Bootstrap)  C++编写 未开源

                  ExtensionClassLoader扩展类加载器(Extension)java编写  父加载器是Bootstrap

                  AppClassLoader:系统类加载器(System)也叫应用类加载器 java编写父加载去是Extension

         用户自定义类加载器

                  CustomClassLoader:继承java.lang.ClassLoader,用户自定义加载方式

注意:这里的父子加载器不一定是继承的关系,也可能是组合的关系

 

图一:双亲加载模式示例

这么做的原因是出于安全考虑,防止用户定义的类加载器加载不合法的,如果加载不了,抛出ClassNotFoundException。

 

 

需要指出的是,加载器之间的父子关系实际上是加载器对象之间的包装关系,而不是类之间的继承关系。一对父子加载器可能是同一个类加载器的两个实例,也可能不是。在子加载器的对象中包装了一个父加载器对象。例如以下loader1和loader2都是MyClassLoader类的实例,并且loader1包装了loader2,loader2是loader1的父加载器 被包装的的父加载器 包装者是子加载器。

 

当生成一个自定义类加载器实例时,如果没有指定它的父类加载器,那么系统类加载器就会成为该类加载器的父加载器

 

双亲模式的问题:顶层ClassLoader无法加载底层ClassLoader

 

双亲模式的破坏:

双亲模式是默认的模式,但并非只能这么做

         Tomcat的WebappClassLoader就会先加载自己的Class,找不到在委托parent

         OSGi的ClassLoader就形成网站结构,根据需要自由加载Class

 

自定义类加载器:

首先查看API得到如下例子


图二:ClassLoader 的API的例子

 

例子:MyClassLoader.java

import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.InputStream; public class MyClassLoader extendsClassLoader {   private String name; //加载类名   private String path = "D:\\"; //加载类的路径   private final String fileType = ".class";      public MyClassLoader(String name) {             super();//系统类为父加载器             this.name = name;         }      public MyClassLoader(ClassLoader loader, String name) {             super(loader); //显示指定父加载器             this.name = name;         }      /**     * 找到指定的类字节码     *@param name 类的名字     */   @Override   public Class<?> findClass(String name) throwsClassNotFoundException {             byte[] data = loadClassData(name);             return defineClass(name, data, 0,data.length); //根据类字节码的ASCII码流  加载类    }    /**     * 读取类的字节码     *@param name 类的名字     *@return 指定类字节码的ASCII码流     */   private byte[] loadClassData(String name) {                  InputStreaminputStream = null;             byte[] data = null;             ByteArrayOutputStream baos = null;             try {                                                   inputStream= new FileInputStream(new File(path + name + fileType));                          baos= new ByteArrayOutputStream();                          intch = 0;                          while((ch = inputStream.read()) != -1) {                                   baos.write(ch);                          }                          data= baos.toByteArray();                  }catch (Exception e) {                  }finally {                          try{                                   inputStream.close();                                   baos.close();                          }catch (Exception e2) {                          }                  }             return data;         }      public static void main(String[] args) throws  Exception  {       // 测试代码         }           public static void test(ClassLoader loader) throws  Exception {                  Class<?>clazz = loader.loadClass("Sample");                  @SuppressWarnings("unused")                  Objectobject = clazz.newInstance();         }                     publicString getName() {                  returnname;         }          publicvoid setName(String name) {                  this.name= name;         }          publicString getPath() {                  returnpath;         }          publicvoid setPath(String path) {                  this.path= path;         }                 publicString getFileType() {                  returnfileType;         }           @Override   public String toString() {             return name;    }}


测试用例:Sample.java:

public class Sample {   public int value = 1;   public Sample() {       System.out.println("Sample loader : " + this.getClass().getClassLoader());       new Dog();    }}


测试用例:Dog.java

public class Dog {   public Dog() {             System.out.println("Dog loader :" + this.getClass().getClassLoader());         }}


图三、文件目录结构

测试1:

MyClassLoaderloader1 = new MyClassLoader("loader1");loader1.setPath("D:\\myapp\\serverlib\\"); MyClassLoaderloader2 = new MyClassLoader(loader1, "loader2");loader2.setPath("D:\\myapp\\clientlib\\"); MyClassLoaderloader3 = new MyClassLoader(null, "loader3");//使用根加载器loader3.setPath("D:\\myapp\\otherlib\\");test(loader2);test(loader3);  

       

图四:测试结果

图五:loader2加载顺序

对于D:\myapp\syslib>javaMyClassLoader

从图中可以看,从上往下加载,只有在loader1才能找到Sample

 

对于java -cp.;D:\myapp\otherlib  MyClassLoader

通过改变了classpath  在classpath中找到了Sample,所以直接用System加载

 

图六:loader3加载顺序

无论怎么变classpath  都只有;loader3才能加载Sample类

 

 

类的卸载

当一个类被加载,连接和初始化后,它的生命周期就开始了。当代表这个类的Class对象不在被引用,即不可触及时Class对象就会结束生命周期,这个类在方法区内的数据也会被卸载,从而结束这个类的生命周期。由此可见,一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期。

 

由java虚拟机自带的类加载器(根类加载器,扩展类加载器,系统类加载器),在虚拟机的生命周期中始终不会被卸载,原因是虚拟机会引用这三个类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些类对象始终是可触及的。

用用户自定义的类加载器所加载的类是可以卸载的

测试代码:

MyClassLoaderloader1 = new MyClassLoader("loader1");loader1.setPath("D:\\myapp\\serverlib\\");Class<?>clazz1 = loader1.loadClass("Sample");@SuppressWarnings("unused")Objectobject = clazz1.newInstance();System.out.println(clazz1.hashCode());/***************************过程1结束***********************/loader1 =null;clazz1 =null;object =null;/***************************过程2结束***********************/loader1 =new MyClassLoader("loader1");loader1.setPath("D:\\myapp\\serverlib\\");clazz1 =loader1.loadClass("Sample");object =clazz1.newInstance();System.out.println(clazz1.hashCode());/***************************过程3结束***********************/Class<?>clazz2 = loader1.loadClass("Sample"); MyClassLoaderloader2 = new MyClassLoader(loader1, "loader2");loader2.setPath("D:\\myapp\\clientlib\\"); Class<?>clazz3 = loader2.loadClass("Sample"); System.out.println("clazz2 == clazz1: " + (clazz2 == clazz1));System.out.println("clazz2 == clazz3: " + (clazz2 == clazz3));System.out.println("clazz3 == clazz1: " + (clazz3 == clazz1));   /** * 结果 * D:\myapp\syslib>java MyClassLoader * Sample loader : loader1 * Dog loader : loader1 * 1311053135 * Sample loader : loader1 * Dog loader : loader1 * 865113938 * clazz2 == clazz1: true * clazz2 == clazz3: true * clazz3 == clazz1: true */


 

 

当执行完过程1,得到的内存模型如下图:


图七:执行完过程1的内存模型

 

执行完过程2,内存中1、2、3连接断开,JVM会卸载加载的类,在执行完过程3之后又会重新加载类,所以两次的clazz1.hashCode()会不同。

 

后面的clazz1,clazz2,clazz3都是由loader1加载的serverlib目录下的类(clazz3本质上还是loader1加载的,之前有分析),在内存中加载一个类只加载一份,所以三个clazz指向的是同一个对象,所以三个比较为TRUE。

原创粉丝点击