ClassLoader以及双亲委托模式

来源:互联网 发布:linux下shell编程技巧 编辑:程序博客网 时间:2024/05/21 14:49

站在虚拟机的角度上,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种就是其它所有的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全部继承java.lang.ClassLoader。

从Java开发人员的角度看,类加载器还可以划分得更细一些,如下:

1.启动类加载器(Bootstrap ClassLoader):这个类加载器不是用java语言所编写的,而是JVM实现的一部分,负责将放置在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定路径中的,并且是虚拟机能识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放置在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接使用。
2.扩展类加载器(Extension ClassLoader):这个类加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
3.应用程序类加载器(Application ClassLoader):这个类加载器由sum.misc.Launcher.$AppClassLoader来实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也被称为系统类加载器。它负责加载用户类路径上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

应用程序由这三种类加载器互相配合进行加载的,如果有必须,还可以加入自己定义的类加载器。这些类加载器之间的关系一般如下图:

   上图中展示的类加载器之间的层次关系,就称为类加载器的双亲委派模型(Parents Delegation Model)。双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当有自己的父类加载器。这里的类加载器之间的父子关系一般不会以继承的关系来实现,而是使用组合关系来复用父加载器的代码。

   为什么要采用双亲委派模型:

   主要是解决类载入过程安全性问题,如果不采用双亲委派模型,用户自己写了一个java.lang.Object类,系统将会出现多个Object类,程序将一片混乱。

   双亲委派模型的式作过程是:

   如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完全这个加载请求时,子加载器才会尝试自己去加载。

   如何打破双亲委派模型:

   打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法,如下例子:

     ①定义Test类

public class Test {  public Test(){    System.out.println(this.getClass().getClassLoader().toString());  }}
    ②重新定义一个继承ClassLoader的TestClassLoaderN类,这个类与前面的TestClassLoader类很相似,但它除了重写findClass方法外还重写了loadClass方法,默认的loadClass方法是实现了双亲委派机制的逻辑,即会先让父类加载器加载,当无法加载时才由自己加载。这里为了破坏双亲委派机制必须重写loadClass方法,即这里先尝试交由System类加载器加载,加载失败才会由自己加载。它并没有优先交给父类加载器,这就打破了双亲委派机制。

public class TestClassLoaderN extends ClassLoader {   private String name;   public TestClassLoaderN(ClassLoader parent, String name) {    super(parent);    this.name = name;  }   @Override  public String toString() {    return this.name;  }   @Override  public Class<?> loadClass(String name) throws ClassNotFoundException {    Class<?> clazz = null;    ClassLoader system = getSystemClassLoader();    try {      clazz = system.loadClass(name);    } catch (Exception e) {      // ignore    }    if (clazz != null)      return clazz;    clazz = findClass(name);    return clazz;  }   @Override  public Class<?> findClass(String name) {     InputStream is = null;    byte[] data = null;    ByteArrayOutputStream baos = new ByteArrayOutputStream();    try {      is = new FileInputStream(new File("d:/Test.class"));      int c = 0;      while (-1 != (c = is.read())) {        baos.write(c);      }      data = baos.toByteArray();    } catch (Exception e) {      e.printStackTrace();    } finally {      try {        is.close();        baos.close();      } catch (IOException e) {        e.printStackTrace();      }    }    return this.defineClass(name, data, 0, data.length);  }   public static void main(String[] args) {    TestClassLoaderN loader = new TestClassLoaderN(        TestClassLoaderN.class.getClassLoader(), "TestLoaderN");    Class clazz;    try {      clazz = loader.loadClass("test.classloader.Test");      Object object = clazz.newInstance();    } catch (Exception e) {      e.printStackTrace();    }  } }