前言:总结一下类加载器与双亲委派模型
基础 类的加载过程指通过一个类的全限定名来获取描述此类的二进制字节流,并将其转化为方法区的数据结构,进而生成一个java.lang.Class对象作为方法区这个类各种数据访问的入口。这个过程通过Java中的类加载器(ClassLoader)来完成。
类与类加载器作用 每个类加载器都有一个独立的类名称空间。当要加载两个类时,如果要比较两个类是否相等(包括equals()方法、isAssignableFrom()方法、isInstance()方法),只有在这两个类被同一个类加载器加载的前提下,比较才有意义。否则,即使两个类来自同一个class文件,被同一个JVM加载,但是加载它们的类加载器不同,则这两个类就不相等。这就相当于两个命名空间中的等价类LoaderA::C和LoaderB::C。
类加载器分类
启动(Bootstrap)类加载器 :负责将%JAVA_HOME%/lib目录中或-Xbootclasspath中参数指定的路径中的,并且是虚拟机识别的(按名称)类库加载到JVM中
扩展(Extension)类加载器 :扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,负责加载%JAVA_HOME%/lib/ext中的所有类库
系统(System)类加载器 :系统类加载器是由Sun的AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,负责加载%CLASSPATH%路径的类库以及其它自定义的ClassLoader
在虚拟机启动的时候会初始化BootstrapClassLoader,然后在Launcher类中去加载ExtClassLoader、AppClassLoader,并将AppClassLoader的parent设置为ExtClassLoader,并设置线程上下文类加载器。
1 2 3 4 5 6 public static void main (String[] args) throws Exception { ClassLoader classLoader = XXX.class .getClassLoader () ; System.out.println(classLoader); System.out.println(classLoader.getParent()); System.out.println(classLoader.getParent().getParent()); }
类加载双亲委派机制概念 sun.misc.Launcher
Launcher是JRE中用于启动程序入口main()的类,下面是其构造器方法源码,AppClassLoader、ExtClassLoader是其静态类(默认限定符,包可见)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public Launcher () { Launcher.ExtClassLoader var1; try { var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader" , var10); } try { this .loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader" , var9); } Thread.currentThread().setContextClassLoader(this .loader); String var2 = System.getProperty("java.security.manager" ); if (var2 != null ) { SecurityManager var3 = null ; if (!"" .equals(var2) && !"default" .equals(var2)) { try { var3 = (SecurityManager)this .loader.loadClass(var2).newInstance(); } catch (IllegalAccessException var5) { } catch (InstantiationException var6) { } catch (ClassNotFoundException var7) { } catch (ClassCastException var8) { } } else { var3 = new SecurityManager(); } if (var3 == null ) { throw new InternalError("Could not create SecurityManager: " + var2); } System.setSecurityManager(var3); } }
VM在加载类时默认采用的是双亲委派机制。即某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归(本质上就是loadClass函数的递归调用)。因此,所有的加载请求最终都应该传送到顶层的启动类加载器中。如果父类加载器可以完成这个类加载请求,就成功返回;只有当父类加载器无法完成此加载请求时,子加载器才会尝试自己去加载。事实上,大多数情况下,越基础的类由越上层的加载器进行加载,因为这些基础类之所以称为“基础”,是因为它们总是作为被用户代码调用的API。。
优点 一句话:双亲委派模型能保证基础类仅加载一次,不会让jvm中存在重名的类,保证了java的安全与稳定性
双亲委派模型最大的优点就是让Java类同其类加载器具备带优先级的层次关系。举例。比如我们要加载顶层的Java类—即java.lang.Object类,无论我们用哪个类加载器去加载Object类,这个加载请求最终都会委托给Bootstrap ClassLoader,这样就保证了所有加载器加载的Object类都是同一个类。如果没有双亲委派模型,那就乱了套了,完全可以搞出Root::Object和S1::Object这样两个不同的Object类。
代码实现 java.lang.ClassLoader的loadClass.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); if (c == null ) { long t0 = System.nanoTime(); try { if (parent != null ) { c = parent.loadClass(name, false ); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null ) { long t1 = System.nanoTime(); c = findClass(name); sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
自定义加载器 继承ClassLorder,实现findClass(String name)方法,即可完成一个带有双亲委派机制的加载器,不能继承AppClassLoader和ExtClassLoader,因为这两个是Launcher的静态内部类,默认访问权限
1、自定义ClassLoader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 package com.husky.demo.annotation;import java.io.ByteArrayOutputStream;import java.io.FileInputStream;import java.io.IOException;import java.util.HashSet;import java.util.Set;import java.util.Timer;import java.util.TimerTask;public class MyClassLoader extends ClassLoader { private String path; private Set<String> useMyClassLoaderLoad; public MyClassLoader (String path, Set<String> useMyClassLoaderLoad) { this .path = path; this .useMyClassLoaderLoad = useMyClassLoaderLoad; } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { Class<?> c = findLoadedClass(name); if (c == null && useMyClassLoaderLoad.contains(name)) { c = findClass(name); if (c != null ) { return c; } } return super .loadClass(name); } @Override protected Class<?> findClass(String name) { byte [] classBytes = getClassByte(name); return defineClass(name, classBytes, 0 , classBytes.length); } private byte [] getClassByte(String name) { String className = name.substring(name.lastIndexOf('.' ) + 1 , name.length()) + ".class" ; try { FileInputStream fileInputStream = new FileInputStream(path + className); byte [] buffer = new byte [1024 ]; int length = 0 ; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); while ((length = fileInputStream.read(buffer)) > 0 ) { byteArrayOutputStream.write(buffer, 0 , length); } return byteArrayOutputStream.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return new byte []{}; } public static void main (String[] args) { new Timer().schedule(new TimerTask() { @Override public void run () { String path = MyClassLoader.class.getResource("").getPath() + "/"; String className = "com.husky.demo.annotation.ServiceTest" ; Set<String> set = new HashSet<String>(); set.add(className); MyClassLoader myClassLoader = new MyClassLoader(path, set); try { Object o = myClassLoader.loadClass(className).newInstance(); o.getClass().getMethod("printVersion" ).invoke(o); } catch (Exception e) { e.printStackTrace(); } } }, 0 , 2000 ); } }
2、ServiceTest
1 2 3 4 5 6 7 8 9 10 public class ServiceTest { public void printVersion () { System.out.println("当前版本:1.1V" ); } }
3、运行
运行过程中修改ServiceTest的printVersion方法,idea能自动编译,实现了加载并替换