一、什么是类加载器(ClassLoader)
类加载器可以把类加载到Java虚拟机中,对于任意的一个类,都需要由加载它的类加载器和这个类本身一同确立起在Java虚拟机中的唯一性
。
比较两个类是否”相等“,只有在这两个类是由同一个类加载器加载的前提在才有意义,否则,即使这两个类源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类必定不相等。这里所指的“相等”包括代表类的Class对象的equal方法、isAssignableFrom()、isInstance()方法及instance关键字返回的结果。

如上图,此时虽然是同一个Person.class ,但是被不同的类加载器加载到Java虚拟机,那么加载后的这两个 Person类是不”相等“,因为它们分别在两个不同的命名空间中。
注:如果上面两个类的是否”相等“比较你没看懂的话,后面自定义类加载器的时候我们会演示一下这个例子,进行简单说明。
二、类加载器分类
一张图看懂类加载器分类:

上图的类加载器主要分为四类:
Bootstrap ClassLoader : 启动类加载器,这
个类加载器使用C++语言实现,是虚拟机自身的一部分
Extension ClassLoader : 扩展类加载器
Application ClassLoader :应用程序类加载器
User ClassLoader :自定义类加载器
public
class
ClassLoaderDemo
{
public
static
void
main
(
String
[
]
args
)
{
// ClassLoaderDemo 的类加载器
System
.
out
.
println
(
ClassLoaderDemo
.
class
.
getClassLoader
(
)
)
;
// 打印每个 ClassLoader的父加载器
ClassLoader classLoader
=
ClassLoaderDemo
.
class
.
getClassLoader
(
)
;
while
(
classLoader
!=
null
)
{
System
.
out
.
println
(
classLoader
)
;
classLoader
=
classLoader
.
getParent
(
)
;
}
System
.
out
.
println
(
classLoader
)
;
}
}
--
-
打印结果:
sun
.
misc
.
Launcher$AppClassLoader
@18b4aac2
sun
.
misc
.
Launcher$AppClassLoader
@18b4aac2
sun
.
misc
.
Launcher$ExtClassLoader
@1540e19d
null
启动类加载器(根加载器)
主要负责加载存放在JAVA_HOME/jre/lib/rt.jar里面所有的class文件,或者被-Xbootclasspath参数所指定路径中以rt.jar命名的文件。启动类加载器无法被Java程序直接引用,如果在编写自定义类加载时,需要把加载的请求委派给启动类加载器,那么直接使用null代替即可。
例如:
MyClassLoader myClassLoader
=
new
MyClassLoader
(
null
)
;
//父加载器使用启动类加载器
public
MyClassLoader
(
ClassLoader parent
)
{
super
(
parent
)
;
//指定该类加载器的父类加载器
}
扩展类加载器
这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载AVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。
应用程序类加载器
这个加载器由sun.misc.Launcher$AppClassLoader实现,它负责加载classpath对应的jar及目录。如果应用程序没有自定义过自己的类加载器,
一般情况下这个就是程序中默认的类加载器。
自定义类加载器
用户定制自己的类加载器,继承ClassLoader,根据自己的需要进行设计和实现,比如需要从指定的路径下读取class文件进行加载等。
知道上面这些内容,你就应该联想到为什么在学习Java的时候首先要配置Java环境变量了。这就是知其所以然的过程。
思考
:这么多类加载器,那么如何保证一个类不被重复加载,只被加载一次呢?主要是通过双亲委派机制
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有父类加载器反馈自己无法完成这个加载请求(它搜索的范围中没有找打所需的类)时,子加载器才会尝试自己去加载。
如下示意图:

在说明一下: 加载一个类的时候。首先自定义类加载类器先会委派给应用程序类加载器(Application ClassLoader),应用程序类加载器会委派给扩展类加载器(Extension ClassLoader),扩展类加载器会委派给启动类加载器(Bootstrap ClassLoader)去加载,这时候如果启动类加载器加载成功,则加载结束。如果加载失败,则交给扩展类加载器去加载,如果扩展类加载器加载成功,则加载结束。如果加载失败,则交给应用程序类加载器,如果应用程序类加载器加载成功,则加载结束。如果加载失败,则交给自定义类加载器。如果自定义类加载器加载成功,则加载结束。否则加载失败,会报ClassNotFoundExecption异常,结束。
每个类加载器都有自己的管辖范围(命名空间),并在自己的管辖范围做好自己的事情
。
双亲委派模型还有一个优点是能提供软件系统的安全性,在这个机制下,用户自定义的类加载器不可能加载应该由父类加载器加载的可靠类,从而防止不可靠甚至是恶意的代码代替父类加载器加载可靠代码。如 java.land.Object 类总是由启动类加载器进行加载,其他任何用户自定义的类加载器都不可能加载含有恶意代码的java.land.Object 类。