类加载器的父亲委托机制深度详解

一、类加载器
1、父亲委托机制(Parent Delegation)
类加载器用来把类加载到Java虚拟机中。从JDK1.2版本开始,类的加载过程采用父亲委托机制,这种机制能够更好的保证java平台的安全,在此委托机制中,除了Java虚拟机自带的根类加载器外,其余的类加载器都有且只有一个父类加载器。当java程序请求加载器loader加载Sample类时,loader首先委托自己的父加载器去加载Sample类,若加载器能加载,则由父加载器完成加载任务,则由父加载器完成加载任务,否则,才有加载器loader本身加载Sample类。
父加载器不是继承关系。也就是说子加载器不一定是继承了父加载器。
2、java虚拟机自带的几种加载器
(1) 根(Bootstrap)类加载器:该类加载器没有父加载器,他负责加载虚拟机的核心类库,如java.lang.*等。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器的实现依赖于底层操作系统,属于虚拟机的实现的一部分,他并没有继承java.lang.ClassLoader类。
(2)扩展(Extension)类加载器:它的父类加载器为根类加载器。他从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre\lib\ext子目录(扩展目录)下加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动有扩展类加载器加载。扩展类加载器是纯java类,是java.lang.ClassLoader类的子类。
(3) 系统(System)类加载器:也称为应用加载器,他的父类加载器为扩展类加载器。他从环境变量classpath或者系统属性java.class.path所指定的目录中加载类。他是用户自定义的类加载器的默认父加载器。系统类加载器是纯java类,是java.lang.ClassLoader子类。

除了以上虚拟机自带的加载器外,用户还可以定制自己的类加载器。java提供了抽象类java.lang.ClassLoader,所有用户自定义的类加载器应该继承ClassLoader类。
他们之间的关系为:
这里写图片描述
在父亲委托机制中,各个机制按照父子关系形成了树形结构,除了根类加载器意外,其余的类加载器有且只有一个父加载器。
这里写图片描述
Class smapleClass=loader2.loadClass(“Sample”);
loader2首先从自己的命名空间中查找Sample类是否已经被加载,如果已经加载就直接返回代表Sample类的Class对象的引用。
如果Sample类还没有被加载,loader2首先请求loader1代为加载,loader1在请求系统类加载器代为加载,系统类加载器在请求扩展类加载器代为加载,扩展类加载器在请求根类加载器代为加载,若根类加载器和扩展类加载器都不能加载,则系统类加载器尝试加载,若能加载成功,则将Sample类所对顶的Class对象的引用返回给loader1,loader1在将引用返回给loader2,从而成功将Sample类加载进虚拟机。若系统类加载器不能加载Sample类,则loader1尝试加载Sample类,若loader1也不能成功加载,则loader2尝试加载,所所有的父加载器以及loader2本身都不能加载,则抛出ClassNotFoundException异常。
若有一个类加载器能成功加载Sample类,那么这个类加载器被称为定义类加载器,所有能成功返回Class对象的引用的类加载器(包括定义类加载器)以及所有子类加载器都被成为初始类加载器。
例如:loader1实际加载了Sample类,则loader1为Sample类的定义类加载器,loader2和loader1为Sample类的初始类加载器。
加载器之间的父子关系实际上指的是加载器对象之间的包装关系,而不是类之间的继承关系。一对父子加载器可能是同一个加载器类的两个实例,也可能不是。在子类加载器对象中包装了一个父类加载器对象。例如一下loader1和loader2都是MyClassLoader类的实例,并且loader2包装了loader1,loader1是loader2的父类加载器。

ClassLoader loader1=new MyClassLoader();
//参数loader1将作为loader2的父加载器
ClassLoader loader2=new MyClassLoader(loader1);

当生成一个自定义的类加载器实例时。如果没有指定他的父加载器,那么系统类加载器将成为该类加载器的父加载器。
API中解释:
Creates a new class loader using the ClassLoader returned by the method getSystemClassLoader() as the parent class loader.
父亲委托机制的优点是能够提高软件系统的安全性。因此在此机制下,用户自定义的类加载器应该有父类加载器加载的可靠类,从而防止不可靠甚至恶意的代码代替由父加载器加载的可靠代码。例如:java.lang.Object类总是由根类加载器加载,其他任何用户自定义的类加载器都不可能加载含有恶意代码的java.lang.Object类。
3、命名空间
每个类加载器都有自己的命名空间,命令空间由该加载器及其所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。
4、运行时包
由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看他们的包名是否相同,还要看定义类加载器是否相同。只有属于同意运行时包的类才能互相访问包可见(即默认访问级别)的类和类成员。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。建设用户自己定义了一个类java.lang.SPy,并用用户自定义的类加载器加载,由于java.lang.Spy和核心类库java.lang.*由不同的加载器加载,他们属于不同的运行时包,所以java.lang.Spy不能访问核心类库java.lang包中的包可见成员。
5、创建用户自定义的类加载器
要创建用户自己的类加载器,只需要继承扩展java.lang.ClassLoader类,然后覆盖他们的findClass(String name)方法即可,该方法根据参数指定的类的名字,返回对应的Class对象的引用。
6、实例:

public class MyClassLoader extends ClassLoader {
      private String name;  //类加载器的名字

      private String path="d:\\";  //加载器的路径

      private final String fileType=".class";//class文件扩展名

      public MyClassLoader(String name){

            super();//让系统类加载器成为该加载器的父加载器

            this.name=name;
      }

      public MyClassLoader(ClassLoader parent,String name){
            super(parent);//显示指定该类加载器的父加载器

            this.name=name;
      }
      @Override
      public String toString() {
            return this.name;
      }
      public String getPath() {
            return path;
      }
      public void setPath(String path) {
            this.path = path;
      }
      @Override
      protected Class<?> findClass(String name) throws ClassNotFoundException {
            // TODO Auto-generated method stub
            byte[] data=loadClassData(name);
            return this.defineClass(name, data, 0,data.length);
      }
      private byte[] loadClassData(String name){
            InputStream is=null;
            byte[] data =null;
            ByteArrayOutputStream baos=null;
            this.name=this.name.replace(".", "\\");
            try {
                  is=new FileInputStream(new File(path+name+fileType));
                  baos=new ByteArrayOutputStream();
                  int ch=0;
                  while ((ch=is.read())!=-1) {
                        baos.write(ch);
                  }
                  data=baos.toByteArray();
            } catch (Exception e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
            }finally {
                  try {
                        is.close();
                        baos.close();
                  } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                  }
            }
            return data;
      }

      public static void main(String[] args) throws Exception{
            MyClassLoader loader1=new MyClassLoader("loader1");

            loader1.setPath("d:\\myapp\\serverlib\\");

            MyClassLoader loader2=new MyClassLoader(loader1,"loader2");

            loader2.setPath("d:\\myapp\\clientlib\\");

            MyClassLoader loader3=new MyClassLoader(null,"loader3");//null指根类加载器,加载核心类库

            loader3.setPath("d:\\myapp\\otherlib\\");

            test(loader2);

            test(loader3);
      }

      public static void test(ClassLoader loader) throws Exception{
            Class clazz=loader.loadClass("com.jvm.classloader.Sample");

            Object object=clazz.newInstance();
      }

}

Sample.java:

public class Sample {
      public int v1=1;

      public Sample(){
            System.err.println("Sample is loaded by : "+ this.getClass().getClassLoader());
          new Dog();
      }
}

Dog.java:

public class Dog {
      public Dog(){
            System.err.println("Dog is loaded by : "+ this.getClass().getClassLoader());
      }
}

class文件的路径:
serverlib文件下:Dog.class,Sample.class
clientlib文件夹下没有文件
otherlib文件夹下:Dog.class,Sample.class
syslib文件夹下:MyClassLoader.class

运行结果:
这里写图片描述

java -cp是制定系统类加载器环境变量classpath的路径

这里写图片描述
当执行loader2.loadClass(“Sample”)时,先由它上层的所有父加载器尝试加载Sample类。loader1从D:/myapp/serverlib目录下成功的加载了Sample类,因此loader1是Sample类的定义了加载器,loader1和loader2是Sample类的初始类加载器。
当执行了loader3.loadClass(“Sample”)时,先由它上层的所有父加载器尝试加载Sample类。loader3的父加载器为根类加载器,他无法加载Sample类,接着loader3从D:/myapp/otherlib目录下成功的加载了Sample类,因此loader3是Sample类的定义类加载器及初始类加载器。
这里写图片描述
在laoder1和loader3各自的命名空间中都存在Sample类和Dog类。
在Sample类中主动使用了Dog类,当执行Sample类的构造方法中的new Dog()语句时,Java语句虚拟机需要先加载Dog类,到底哪个类加载器加载呢?从步骤(1)的打印结果可以看出,加载Sample类的loader1还加在了Dog类,Java虚拟机会用了Sample类的定义加载器加载Dog类,加载过程同样采取了父亲委托机制,喂了验证这一点,可以把D:\myapp\serverlib下的Dog.class,文件删除,然后再D:\myapp\syslib目录下放一个Dog.class文件,此时程序的打印结果为:
这里写图片描述
由此可见,当由Loader1加载的Sample类首次主动使用Dog类时,Dog类由系统类加载器加载。如果把D:\myapp\serverlib和D:\myapp\syslib目录下的Dog.class文件都删除,然后再D:\myapp\clientlib目录下存放一个Dog.class文件,当由loader1加载的Sample类首次主动使用Dog类时,由于loader1及它的父加载器都无法加载Dog类,因此test(loader2)方法会抛出ClassNotFundException。
6、不同类加载器的命名空间的关系:
同一命名空间内的类是互相可见的。
子加载器的命名空间包含所有父加载器的命名空间。因此由子加载器加载的类看见父加载器加载的类。例如系统泪加载器加载的类能看见根类加载器加载的类。
由父类加载器加载的类不能看见子类加载器加载的类。
如果两个加载器之间没有直接或间接的父子关系,那么他们各自加载的类互相不可见。、
当两个不同命名空间的类互相不可见时,可采用java的反射机制来访问对方的属性和方法。
二、类的卸载
当Sample类被加载、连接和初始化后,他的生命周期就开始了。当代表Sample类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,当Samle类在方法区内的数据也会被卸载,从而结束Sample类的生命周期。由此可见,一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期。
由Java虚拟机自带的类加载器所加载的类,自虚拟机的生命周期中,始终不会被卸载。Java虚拟机自带的类加载器包括根类加载器,扩展类加载器和系统类加载器。Java虚拟机本身会始终引用这些类加载器,而这些类加载器则会使用他们所加载的类的Class对象,因此这些Class对象始终是可触及的。
由用户自定义的类加载器所加载的类是可以被卸载的。
一个类的实例总是引用代表着这个类的Class对象。在Object中定义了getClass()方法,这个方法返回代表所属泪的Class对象的引用。此外,所有的Java对象都有一个静态属性class,他引用代表这个类的Class对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值