8-ClassLoader

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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值