类加载器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()方法中也会按照双亲委派去找寻父类加载器,不符合要求。(看不懂源码没有关系,里面的英文还是可以看懂的嘛哈哈)

 

于是我的步骤

  1. 编写一个类继承自ClassLoader抽象类。 
  2. 复写它的findClass()方法,直接调用findClass()来找寻Class文件,而不是loadClass()。
  3. findClass()方法中调用defineClass(),defineClass()将class二进制内容转换成Class对象并加载进内存。 

这样子,就实现了我的重复加载Class的目的。

 

posted on 2017-03-30 18:11  景大爷  阅读(753)  评论(0编辑  收藏  举报