背景:据说ClassLoader类加载机制是进入BAT的必经之路。java
ClassLoader总述:缓存
普通的Java开发其实用到ClassLoader的地方并很少,可是理解透彻ClassLoader类的加载机制,不管是对咱们编写更高效的代码仍是进BAT都大有裨益;而从“黄埔军校”出来的我对ClassLoader的理解都是借鉴了不少书籍和博客,站在了各大博主的肩膀上,感谢大家!上菜,Classloader最主要的做用就是将Java字节码文件(后缀为.class)加载到JVM中,JVM在启动时不会一次性加载全部的class文件,而是根据须要动态加载class文件,毕竟一次性加载太多jar包的class文件JVM吃不消;下面主要研究Bootstrap ClassLoader、Extention ClassLoader和AppClassLoader这三种类加载器。tomcat
谈到ClassLoader就想到咱们安装JDK的时候都会在控制台输入java、javac验证是否安装成功,而这个javac就是Java ClassLoader,测试是否能把Java源文件正确编译成Java字节码文件,下面的截图就是个javac的小例子,javac以后加载器把Java源文件编译成TestClassLoader.class字节码文件。安全
因为下面要讲到ClassLoader的加载路径,这里顺便把Java的环境变量也复习一遍。网络
JAVA_HOME:app
指的是安装JDK的位置,如:JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home" 。ide
PATH:测试
配置PATH(程序的路径)的做用将就是可以在命令行窗口直接键入程序的名字了,而再也不须要键入它的全路径,好比上面代码中我用的到javac
和java
两个命令。如:PATH=".$PATH:$JAVA_HOME/bin" ;就是在JAVA_HOME路径上添加了JDK下的bin目录便可。
加密
CLASSPATH:spa
CLASSPATH就是指向jar包的路径,如:PATH=".$PATH:$JAVA_HOME/bin" ;"."表示当前目录。
ClassLoader类加载流程:
三个Class Loader的执行顺序是:Bootstrap CLassloder -> Extention ClassLoader -> AppClassLoader;
一、Bootstrap CLassloder是最顶层的加载类,主要是加载核心类库,也就是%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等资源;而且,能够经过启动JVM时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录,下面有个小荔子。
二、Extention ClassLoader是扩展的类加载器,其加载的是目录%JRE_HOME%\lib\ext目录下的jar包和class文件;它一样也能够加载-D java.ext.dirs选项指定的目录。
三、Appclass Loader是用于加载当前应用的classpath的全部类,其也称为SystemAppClass。
另外有兴趣的还能够看下Launcher
类的源码,源码中规定了三个加载器的环境属性分别为B:sun.boot.class.path、E:java.ext.dirs和A:java.class.path;下面经过代码来简单测试写,如图:
打印输出结果:
BootstrapClassLoader:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/resources.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/sunrsasign.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jsse.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jce.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfr.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/classes
ExtClassLoader:
/Users/apple/Library/Java/Extensions:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext:
/Library/Java/Extensions:/Network/Library/Java/Extensions:
/System/Library/Java/Extensions:/usr/lib/java
AppClassLoader:
/TJT/Eclipse/workspace/tjt/bin:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar
为了更好的理解三者之间加载的关系,咱们来测试一个类的加载器和它的父类加载以及一些不是咱们建立的类如String、Double、int等基础类:
从上图中可用看出,本身编写的类Test2.class文件是由AppClassLoader加载的,而且AppClassLoader有父加载器ExtClassLoader,但ExtClassLoader的父加载器为null;Double.class这个Java基础类的加载器为null,其父加载也为空且程序还会报空指针异常错误;其实呢,Double.class是有Bootstrap CLassLoader加载的,也并非每一个加载器都有父加载器;总的来讲就是JVM启动时经过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,诸如一些int.class,String.class都是由它加载;JVM初始化sun.misc.Launcher并建立Extension ClassLoader和AppClassLoader实例,且将ExtClassLoader设置为AppClassLoader的父加载器;而Bootstrap虽然没有父加载器,可是它却能够做为一个ClassLoader的父加载器;另外,一个ClassLoader建立时若是没有指定parent,那么它的parent默认就是AppClassLoader;
双亲委托:
当一个类加载器查找class和resource时,是经过“委托模式”进行的,它首先会判断这个class是否是已经加载成功,若是没有加载的话它并非本身进行查找,而是先经过父加载器,而后递归下去,直到Bootstrap ClassLoader,若是Bootstrap classloader找到了,直接返回,若是没有找到,则一级一级返回,最后是由自身去查找这些对象;这种机制就叫作双亲委托。
从上图可用看出ClassLoader的加载序列,委托是从下往上,查找过程则是从上向下的,如下有几个注意事项:
一、一个AppClassLoader查找资源时,首先会查看缓存是否有,如有则从缓存中获取,不然委托给父加载器;
2.、重复第一步的递归操做,查询类是否已被加载;
3.、若是ExtClassLoader也没有加载过,则由Bootstrap ClassLoader加载,它首先也会查找缓存,若是没有找到的话,就去找本身的规定的路径下,也就是sun.mic.boot.class下面的路径,找到就返回,找不到就让子加载器本身去找。
4.、Bootstrap ClassLoader若是没有查找成功,则ExtClassLoader本身在java.ext.dirs路径中去查找,查找成功就返回,查找不成功则再向下让子加载器找。
5.、如果ExtClassLoader查找不成功,则由ppClassLoader在java.class.path路径下本身查找查找,找到就返回,若是没有找到就让子类找,若是没有子类则会抛出各类异常。
自定义CLassLoader:
在ClassLoader中有四个很重要实用的方法loadClass()、findLoadedClass()、findClass()、defineClass(),能够用来建立属于本身的类的加载方式;好比咱们须要动态加载一些东西,或者从C盘某个特定的文件夹加载一个class文件,又或者从网络上下载class主内容而后再进行加载等。分三步搞定:
一、编写一个类继承ClassLoader抽象类;
二、重写findClass()方法;
三、在findClass()方法中调用defineClass()方法便可实现自定义ClassLoader;
需求:自定义一个classloader其默认加载路径为"/TJT/Code"下的jar包和资源。首先建立一个Test.java,而后javac编译并把生成的Test.class文件放到"/TJT/Code"路径下,而后再编写一个DiskClassLoader继承ClassLoader,最后经过
FindClassLoader的测试类,调用再Test.class里面的一个find()方法。
1 package www.baidu; 2 import java.io.ByteArrayOutputStream; 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.IOException; 6
7 public class DiskClassLoader extends ClassLoader{ 8 //自定义classLoader能将class二进制内容转换成Class对象
9 private String myPath; 10
11 public DiskClassLoader(String path) { 12 myPath = path; 13 } 14
15 //findClass()方法中定义了查找class的方法
16 @Override 17 protected Class<?> findClass(String name) throws ClassNotFoundException{ 18 String fileName = getFileName(name); 19 File file = new File(myPath,fileName); 20 try { 21 FileInputStream is = new FileInputStream(file); 22 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 23 int len = 0; 24 try { 25 while((len = is.read()) != -1) { 26 bos.write(len); 27 } 28 } catch (IOException e) { 29 e.printStackTrace(); 30 } 31 byte[] data = bos.toByteArray(); 32 is.close(); 33 bos.close(); 34 //数据经过defineClass()生成了Class对象
35 return defineClass(name, data,0,data.length ); 36 } catch (Exception e) { 37 e.printStackTrace(); 38 } 39 return super.findClass(name); 40 } 41
42 private String getFileName(String name) { 43 int lastIndexOf = name.lastIndexOf('.'); 44 if (lastIndexOf == -1) { 45 return name + ".class"; 46 }else { 47 return name.substring(lastIndexOf + 1) + ".class"; 48 } 49 } 50 }
测试结果以下:找到了自定义的加载路径而且调用了类中的find()方法
1 package www.baidu; 2 import java.lang.reflect.Method; 3
4 public class FindClassLoader { 5 public static void main(String[] args) throws ClassNotFoundException { 6 //建立自定义classloader对象
7 DiskClassLoader diskL = new DiskClassLoader("/TJT/Code"); 8 System.out.println("classloader is: "+diskL); 9 try { 10 //加载class文件
11 Class clazz = diskL.loadClass("www.baidu.Test"); 12 if (clazz != null) { 13 Object object = clazz.newInstance(); 14 Method declaredMethod = clazz.getDeclaredMethod("find", null); 15 //经过反射调用Test类的find()方法
16 declaredMethod.invoke(object, null); 17 } 18 } catch (Exception e) { 19 e.printStackTrace(); 20 } 21 } 22 }
总结:
除此以外,ClassLoader还能够进行程序加密(好比你写了比较骚的jar包),这样咱们就能够在程序中加载特定了类,而且这个类只能被咱们自定义的加载器进行加载,提升了程序的安全性,可是用的很少;反正咱们在项目上是不容许用ClassLoader加密,宁愿裸奔,了解一下。另外就是tomcat的类加载机制也是遵循双亲委派机制的,而且大部分的加载机制和JVM类加载机制同样,理解了Bootstrap ClassLoader、Extention ClassLoader和AppClassLoader这三种加载器后再看tomcat类的加载就能够横着走了。