要理解jvm中的类加载器结构,仅仅查阅文档是不够的。这里给出一个小程序帮助理解jvm虚拟机中的类加载器结构。html
package com.wuyue.demo; import java.util.Date; import java.util.List; /** * 测试类 * @author wuyue */ public class JVMClassLoader { public static void main(String[] args){ System.out.println("JVMClassLoader类的加载器的名称:"+JVMClassLoader.class.getClassLoader().getClass().getName()); System.out.println("System类的加载器的名称:"+System.class.getClassLoader()); System.out.println("List类的加载器的名称:"+List.class.getClassLoader()); ClassLoader cl = JVMClassLoader.class.getClassLoader(); while(cl != null){ System.out.print(cl.getClass().getName()+"->"); cl = cl.getParent(); } System.out.println(cl); } } 复制代码
而后咱们编译并运行这段程序查看下这段代码的运行结果:java
点此查阅jdk文档c++
咱们能够查阅jdk中的函数说明,发现有这么一段话:bootstrap
Returns the class loader for the class. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class was loaded by the bootstrap class loader.小程序
讲白了,这里的意思就是有的虚拟机实现会用null 来代替bootstrap这个classloader。api
BootStrap->ExtClassLoader->AppClassLoader->开发者自定义类加载器. 可认为BootStrap为祖先加载器,开发者自定义类加载器为底层加载器。 不过多数状况,咱们并不会自定义类加载器,因此大多数状况,AppClassLoader就是JVM中的底层类加载器了。数组
注意BootStrap是用c++代码编写的,后面2个类加载器则是java类编写 这就解释了为何BootStrap加载器会返回null了,由于这个祖先类加载器在 java里根本找不到吗bash
不少人学习类加载器只是浏览一遍文档结束,很难有深入的映像,时间一久就忘记,因此下面给出一个例子,能够加深对类加载器委托机制的印象markdown
这里咱们能够看到,我是先将编译好的class文件 打成一个jar包,而后再将这个打好的jar包放到咱们jdk路径下的 /jre/lib/ext/ 这个目录下,前面介绍过这个目录就是ext类加载器要加载的目录,而后再次运行咱们一开始编写好的程序就能够发现,一样是JVMClassLoader这个类,一开始咱们的类加载器是appclassloader后面就变成了extclassloader。到这里应该就对类加载器的委托机制有了深入认识了。oracle
简单来讲,一句话归纳jvm中的类加载器机制:
能够用爸爸的钱就绝对不用本身的钱,若是爸爸没有钱,再用本身的, 若是本身仍是没有钱,那么就classnotfound异常
好处就是要加载一个类首先交给他的上级类加载器处理,若是上级类有,就直接拿来用,这样若是以前加载过的类就不须要再次重复加载了。简称:能啃老用爹的钱,为啥要用本身的?
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) { //先看有没有爸爸类加载器若是有就继续“递归”调用loadclass这个方法 c = parent.loadClass(name, false); } else { //若是没有爸爸类加载器了,就说明到头了。看看 //祖先bootstrap类加载器中有没有 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); //若是没有找到就调用本身的findclass找这个类。 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } 复制代码
因此看到在代码里其实就是一个调用parent loadclass的过程,若是parent都找不到就调用本身的findclass方法来找。 和咱们前面的分析是一致的。
有兴趣的同窗能够在jdk目录中找到rt.jar 这个jar包,查看AppClassLoader等系统自带的classLoader的源码,有助于加深理解,这里就再也不过多叙述了
首先咱们定义一个CustomDate类,这个类只重写一下toString方法
package com.wuyue.test; import java.util.Date; /** * 只是重写了Date的toString方法 */ public class CustomDate extends Date{ @Override public String toString() { return "my cystom date"; } } 复制代码
而后写一个简单的classloader,自定义的那种。
package com.wuyue.test; import java.io.*; public class MyClassLoader extends ClassLoader{ String classDir; public MyClassLoader() { } public MyClassLoader(String classDir) { this.classDir = classDir; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String classFile=classDir+"/"+name+".class"; System.out.println("classFile path=="+classFile); try { //这个地方咱们只是简单的读取文件流的方式来获取byte数组 //其实能够尝试将class文件加密之后 这里解密 这样就能够保证 //这种class文件 只有你写的classloader才能读取的了。 //其余任何classloader都读取不了 包括系统的。 byte[] classByte=toByteArray(classFile); return defineClass(classByte,0,classByte.length); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return super.findClass(name); } /** * the traditional io way * * @param filename * @return * @throws IOException */ public static byte[] toByteArray(String filename) throws IOException, FileNotFoundException { File f = new File(filename); if (!f.exists()) { throw new FileNotFoundException(filename); } ByteArrayOutputStream bos = new ByteArrayOutputStream((int) f.length()); BufferedInputStream in = null; try { in = new BufferedInputStream(new FileInputStream(f)); int buf_size = 1024; byte[] buffer = new byte[buf_size]; int len = 0; while (-1 != (len = in.read(buffer, 0, buf_size))) { bos.write(buffer, 0, len); } return bos.toByteArray(); } catch (IOException e) { e.printStackTrace(); throw e; } finally { try { in.close(); } catch (IOException e) { e.printStackTrace(); } bos.close(); } } } 复制代码
最后写一个测试咱们自定义classloader的主程序:
package com.wuyue.test; import java.util.Date; public class ClassLoaderTest { public static void main(String[] args) { try { Class classDate = new MyClassLoader("/Users/wuyue/IdeaProjects/ClassLoaderTest/out/production/ClassLoaderTest/com/wuyue/test").loadClass("com.wuyue.test.CustomDate"); Class classDate2 = new MyClassLoader("/Users/wuyue/IdeaProjects/ClassLoaderTest/out/production/ClassLoaderTest/com/wuyue/test").loadClass("CustomDate"); Date date = (Date) classDate.newInstance(); System.out.println("date ClassLoader:"+date.getClass().getClassLoader().getClass().getName()); System.out.println(date); Date date2 = (Date) classDate2.newInstance(); System.out.println("date2 ClassLoader:"+date2.getClass().getClassLoader().getClass().getName()); System.out.println(date2); } catch (Exception e1) { e1.printStackTrace(); } } } 复制代码
而后咱们来看一下程序运行结果:
你们能够看到classdate和classDate2 这2个类,咱们在用classLoader去加载的时候传的参数惟一的不一样就是前者传入了完整的包名,然后者没有。这就致使了前者的classLoader依旧是系统自带的appclassloader 然后者才是咱们自定义的classloader。 缘由:
虽然对于classDate和classDate2来讲,咱们手动指定了她的类加载是咱们自定义的myclassloader,可是根据类加载器的规则,咱们能用父亲的loadclass就确定不会用本身的,而咱们系统类加载器,AppClassLoader要想loadclass成功是须要传入完整的包名的。因此classDate的构造仍是传入了完整的包名,这就是为啥classDate的加载器仍是AppClassLoader,可是classDate2并无传入完整的包名,因此AppClassLoader也是找不到这个CustomDate类的,最后只能交给MyClassLoader这个最底层的,咱们自定义的classloader来load