话不多说,干就完了。

回顾一下前面的文章,了解了类加载子系统。整个类加载过程为加载、验证、准备、解析、初始化、使用、卸载这么七个过程。每个过程都做了详细的介绍。
在加载阶段,它做了三件事情:
- 通过类的全限定名称加载class字节码文件
- 将类信息的静态存储结构保存到方法区
- 在内存中生成一个java.lang.Object对象作为方法区访问该类信息的入口
类加载器
本篇讲解的都是第一个过程,加载字节码文件。其关键就是类加载器,由类加载器来负责把class字节码文件加载到内存中。需要注意的是,类加载器虽然只是执行了类加载的动作,但是在Java程序中,并不是说它只会在类加载阶段才会起到作用。
对于任何一个类来说,它在虚拟机中的唯一性都由两个要素来决定:加载它的类加载器及这个类本身。在实际开发或成中,经常会遇到比较两个对象是否为同一个,在这种情况下,只有在这两个类都是由同一个类加载器加载,比较才会有意义。否则,即使两个类是来源于同一个class文件,但是是不同的类加载器所加载,它们也不是相等的或者说是同一个类对象。
在JVM中,支持两种类型的类加载器:引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。严格来讲凡是派生或者继承抽象类ClassLoader的类加载器都属于自定义类加载器。例如Extension ClassLoader和Application ClassLoader。
由上图我们可以看到类加载器中存在三种系统类加载器,分别为引导类加载器、拓展类加载器、应用类加载器。实际上按照刚刚所说的。拓展类加载器、应用类加载器实际上属于自定义类加载器。只不过是它们已经在JVM中实现了。所以又把它们归为系统类加载器。
引导类加载器(Bootstrap ClassLoader)
引导类加载器是非Java语言(C/C++)编写的。主要负责加载**<JAVA_HOME>\lib目录下的类或者是被-Xbootclasspath**参数所指定的路径中的类库。
它并不继承java.lang.ClassLoader,同时它不能被Java程序所引用。为了安全考虑,它只加载包名为java、javax、sun等开头的类。
在编写自定义类加载器的时候,如果需要把类加载请求委托给引导类加载器,直接使用null代替即可。
拓展类加载器(Extension ClassLoader)
由Java语言编写,派生于Classlader,父类加载器为Bootstrap Classloader,主要负责加载<JAVA_HOME>\lib\ext路径下或被java.ext.dirs参数所指定的路径下的类。拓展类加载器可在Java程序中直接使用。
应用类加载器(Application ClassLoader)
由Java语言编写,派生于Classlader,父类加载器为Extension ClassLoader。主要加载用户类路径下的类(ClassPath)。可以在Java程序中直接使用。通常通过ClassLoader中的getSystemClassLoader()方法返回的就是Application ClassLoader,所以它也被称为系统类加载器。通常程序默认的类加载器就是Application ClassLoader。
关于三种类加载器,可以通过下图来直观的展示它们的层级关系:

在实际编码过程中,可以通过以下四种方式获取类加载器:
clazz.getClassLoader(); // 获取当前类的类加载器
Thread.currentThread().getContenxtClassLoader();//获取当前线程上下文的类加载器
ClassLoader.getSystemClassloader();//获取系统的类加载器
DriverManaer.getCallerClassLoader();//获取调用者的类加载器
自定义类加载器(User-Defined ClassLoader)
在程序中,上述三种类加载器相互配合执行,已经能够满足我们的要求,但在实际情况中,可能还会存在其他情况需要我们自己定义类加载器。
- 需要的类可能不一定存放在用户的classPath下,要加载自定义路径的类类文件,可以自定义ClassLoader
- 可能从网络的输入流中读取类,需要做一些加密和解密操作
例如在商业系统中,为了防止别人反编译代码,那么可能会对代码加密来达到防止被反编译。但是又不能影响到程序的正常执行。这个时候可以自定义类加载器,加载前先进行解密,来实现加载被加密过的字节码文件,从而正确的加载类。
如何实现自定义类加载器
- 继承抽象类java.lang.ClassLoader
- 重写findClass()方法
双亲委派机制
在上图类加载器层级关系图中,实际上这种层级关系就被称为双亲委派机制。虚拟机在对class文件进行加载的时候是按需加载的,也就是说只有在使用的时候才会将class字节码文件加载到内存中。所以,在加载类的使用的就是双亲委派机制。
通俗的讲,就是把类的加载请求向上委托,交由父类加载器进行加载处理。
工作原理
- 类加载器在收到类加载请求之后,首先会将该类加载请求向上委托给自己的父类去加载,而并不会直接由自己加载该类。
- 若向上委托的父类加载器还存在父类加载器,则会继续向上委托。直到达到最顶层的启动类加载器。
- 如果父类加载器可以完成类的加载请求,那么就会返回成功,若经过层层向上委托,父类加载器均不能加载此类,则会返回到当前收到请求的子类,由其自己进行加载。
这就是双亲委派机制的原理。上图!

图文结合就能更加清晰的了解双亲委派机制的工作原理了。
为什么要用双亲委派
生活中大家可能都是能自己干的就不麻烦别人,那既然自己能够加载这个类请求,为什么还要去麻烦别人呢?它是能有什么帮助吗?下面就来讲讲为什么要双亲委派。
-
沙箱安全机制
沙箱安全机制就是为了保护程序的安全,防止核心的API被篡改危害Java虚拟机自身的安全。例如java.lang.Object类,它存放在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于最顶层的引导类加载器进行加载。也就限制了无论在什么环境下,Object的加载都是同一个类,如果没有双亲委派机制,用户自定义了一个Object类,并放在ClassPath路径下,那么系统中就会出现多个Object类,那么Java类型体系中最基础的Object类的行为就无法保证。也会导致程序出现问题。
-
避免类的重复加载
由于采用向上委托请求的方式,那么如果是下层的类加载器收到了类的加载请求,通过层层委托。只要某一层能够加载该类,那么就回进行返回,层层委派的机制保证了只会有一个类加载器加载类并返回,就不会出现多个类加载器均能够加载成功并返回。
双亲委派机制并不是一个强制性的约束,它只是一个推荐,可以被破坏的。在说到自定义类加载器的时候,要继承java.lang.ClassLoader并重写findClass方法。这是并不破坏双亲委派机制。如果要破坏双亲委派机制,只需要在自定义类加载器的时候增加重写loadClass方法指定新的类加载逻辑。
不怕路歹行不怕大雨淋,心上一字敢 面对我的梦,甘愿来作憨人。 --<憨人>