本文共 5252 字,大约阅读时间需要 17 分钟。
ClassLoader的双亲委托模式:classloader 按级别分为三个级别:最上级 : bootstrap classLoader(根类加载器) ; 中间级:extension classLoader (扩展类加载器) 最低级 app classLoader(应用类加载器)。
根(Bootstrap)类加载器:该加载器没有父加载器。它负责加载虚拟机的核心类库,如java.lang.*等。例如java.lang.Object就是由根类加载器加载的。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器的实现依赖于底层操作系统,属于虚拟机的实现的一部分,它并没有继承java.lang.ClassLoader类。
扩展(Extension)类加载器:它的父加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器是纯Java类,是java.lang.ClassLoader类的子类。
系统(System)类加载器:也称为应用类加载器,它的父加载器为扩展类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,它是用户自定义的类加载器的默认父加载器。系统类加载器是纯Java类,是java.lang.ClassLoader类的子类。
父子加载器并非继承关系,也就是说子加载器不一定是继承了父加载器。对于Java来说,java 虚拟机要将被用到的java类文件通过classLoader 加载到JVM内存中,而这个classloader就是bootstrap classloader。而对于APP而言,首先请求app 级来加载,继而请求extension classLoader,最后启动bootstrap classLoader 。
public class MyClassLoader extends ClassLoader { //类加载器名称 private String name; //加载类的路径 private String path = "D:/"; private final String fileType = ".class"; public MyClassLoader(String name){ //让系统类加载器成为该 类加载器的父加载器 super(); this.name = name; } public MyClassLoader(ClassLoader parent, String name){ //显示指定该类加载器的父加载器 super(parent); this.name = name; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } @Override public String toString() { return this.name; } private byte[] loaderClassData(String name){ InputStream is = null; byte[] data = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); this.name = this.name.replace(".", "/"); try { is = new FileInputStream(new File(path + name + fileType)); 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 data; } @Override public Class findClass(String name){ byte[] data = loaderClassData(name); return this.defineClass(name, data, 0, data.length); } public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { //loader1的父加载器为系统类加载器 MyClassLoader loader1 = new MyClassLoader("loader1"); loader1.setPath("D:/lib1/"); //loader2的父加载器为loader1 MyClassLoader loader2 = new MyClassLoader(loader1, "loader2"); loader2.setPath("D:/lib2/"); //loader3的父加载器为根类加载器 MyClassLoader loader3 = new MyClassLoader(null, "loader3"); loader3.setPath("D:/lib3/"); Class clazz = loader2.loadClass("Sample"); Object object = clazz.newInstance(); }}public class Sample { public Sample(){ System.out.println("Sample is loaded by " + this.getClass().getClassLoader()); new A(); }}public class A { public A(){ System.out.println("A is loaded by " + this.getClass().getClassLoader()); }}
每一个自定义ClassLoader都必须继承ClassLoader这个抽象类,而每个ClassLoader都会有一个parent ClassLoader,我们可以看一下ClassLoader这个抽象类中有一个getParent()方法,这个方法用来返回当前 ClassLoader的parent,注意,这个parent不是指的被继承的类,而是在实例化该ClassLoader时指定的一个 ClassLoader,如果这个parent为null,那么就默认该ClassLoader的parent是bootstrap classloade。
上面讲解了一下ClassLoader的作用以及一个最基本的加载流程,接下来我们说说ClassLoader使用了双亲委托模式进行类加载。
通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
为了更好的理解双亲委托模式,我们先自定义一个ClassLoader,假设我们使用这个自定义的ClassLoader加载 java.lang.String,那么这里String是否会被这个ClassLoader加载呢?
事实上java.lang.String这个类并不会被我们自定义的classloader加载,而是由bootstrap classloader进行加载,为什么会这样?实际上这就是双亲委托模式的原因,因为在任何一个自定义ClassLoader加载一个类之前,它都会先 委托它的父亲ClassLoader进行加载,只有当父亲ClassLoader无法加载成功后,才会由自己加载。而在上面的例子中,因为 java.lang.String是属于java核心API的一个类,所以当使用自定义的classloader加载它的时候,该 ClassLoader会先委托它的父亲ClassLoader进行加载(bootstrap classloader),所以并不会被我们自定义的ClassLoader加载。
我们来看一下ClassLoader的一段源码:
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{ // 首先检查该name指定的class是否有被加载 Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { //如果parent不为null,则调用parent的loadClass进行加载 c = parent.loadClass(name, false); }else{ //parent为null,则调用BootstrapClassLoader进行加载 c = findBootstrapClass0(name); } }catch(ClassNotFoundException e) { //如果仍然无法加载成功,则调用自身的findClass进行加载 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
那么我们使用双亲委托模式有什么好处呢?
附: