类加载器ClassLoader的理解
最近在做一个热加载Class的小组件,这个组件需要对类加载器ClassLoader有所了解,我就顺便借这个机会把学到的一点皮毛与大家分享一下。
从Class文件开始
ClassLoader,顾名思义就是类加载器。简单的说就是把Class文件加载到JVM中,之后程序就能正常的运行了。
我们平时写的代码都是.java格式的文件,但是这个文件是不能够直接运行的。比如下面这个简单的测试程序Test.java
1 public class Test { 2 3 public static void main(String[] args) { 4 System.out.println("Testing"); 5 } 6 }
在文件夹下看,就是这么一个文件:
我们在命令行下编译一下这个Java文件:
1 javac Test.java
然后就可以在目录下看到生成的class文件,也就是JVM可以识别运行的文件。
Java自带的类加载器
Java自带有三个类加载器:
Bootstrap ClassLoader:主要加载核心类库,%Java_HOME%\lib 下的rt.jar、resources.jar、charsets.jar和class等,总之就是jdk安装目录下lib目录下的一些核心jar包。
Extention ClassLoader 扩展的类加载器,主要加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件,就是JRE安装目录下lib目录下ext目录下的jar和class文件。
Appclass Loader也称为SystemAppClass应用程序加载器 加载当前应用的classpath的所有类,这个就是很好理解了,就是我们平时自己写的项目类路径下的类。
双亲委派机制
接下来我先祭出一张常见的图:
双亲委派机制的意思是:一个类加载器遇到一个加载类的请求,首先不会自己去加载,而是看自己的父加载器能否加载这个类,然后一级一级往上传直到遇到能够加载这个类的加载器。
简单可以形容为一家几口吃苹果:
小明:爸爸,这苹果你吃吗?
小明的爸爸:我先问问你爷爷,爸,这苹果你吃吗?
小明的爷爷:你们真孝顺,那我就恭敬不如从命啦。
如果遇到顶层的父类加载器无法加载,该怎么办?这个时候,就往下找,找到第一个能加载这个类的加载器
小明:爸爸,这苹果你吃吗?
小明的爸爸:我先问问你爷爷,爸,这苹果你吃吗?
小明的爷爷:牙不好啦,我不吃啦你们吃吧。
小明的爸爸:好的,我的牙口还不错,那我就吃了。
那么为什么需要一个机制呢,原因主要是为了避免类加载程序的混乱。
比如Java官方指定了一个java.lang.String类,然后你自己又重新写了一个java.lang.String类的类。这个时候,你想import一个String类型,就没有办法判断到底这个String是哪个String了。
当然这边要多说一句,父类加载器不等于父类,也就是上面的三个类加载器并不是继承关系。
自定义ClassLoader
刚刚那张图大家应该已经发现了,除了三个系统自带的ClassLoader以外,最底层还有几个自定义的类加载器。
没错,原来的类加载器只是按照特定的方式加载指定目录下的jar包,那么如果你想按照自己的要求加载一些东西呢?这就需要你自己去定义一个classloader了。
比如以我为例,我希望可以多次重新加载同一个Class。
ClassLoader中,加载Class的方法是loadClass(),我们可以看一下源码:
1 protected Class<?> loadClass(String name, boolean resolve) 2 throws ClassNotFoundException 3 { 4 synchronized (getClassLoadingLock(name)) { 5 // First, check if the class has already been loaded 6 Class<?> c = findLoadedClass(name); 7 if (c == null) { 8 long t0 = System.nanoTime(); 9 try { 10 if (parent != null) { 11 c = parent.loadClass(name, false); 12 } else { 13 c = findBootstrapClassOrNull(name); 14 } 15 } catch (ClassNotFoundException e) { 16 // ClassNotFoundException thrown if class not found 17 // from the non-null parent class loader 18 } 19 20 if (c == null) { 21 // If still not found, then invoke findClass in order 22 // to find the class. 23 long t1 = System.nanoTime(); 24 c = findClass(name); 25 26 // this is the defining class loader; record the stats 27 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); 28 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 29 sun.misc.PerfCounter.getFindClasses().increment(); 30 } 31 } 32 if (resolve) { 33 resolveClass(c); 34 } 35 return c; 36 } 37 }
可是在ClassLoader中的加载class的方法loadClass()中,有一个 findLoadedClass() 方法来判断Class是否已经被加载的操作,不符合要求,而且loadClass()方法中也会按照双亲委派去找寻父类加载器,不符合要求。(看不懂源码没有关系,里面的英文还是可以看懂的嘛哈哈)
于是我的步骤
- 编写一个类继承自ClassLoader抽象类。
- 复写它的
findClass()
方法,直接调用findClass()来找寻Class文件,而不是loadClass()。 - 在
findClass()
方法中调用defineClass(),defineClass()将class二进制内容转换成Class对象并加载进内存
。
这样子,就实现了我的重复加载Class的目的。