java类加载器
首先,我们知道,虚拟机在加载类的过程中需要使用类加载器进行加载,而在Java中,类加载器有很多,那么当JVM想要加载一个.class文件的时候,到底应该由哪个类加载器加载呢?
首先,我们需要知道的是,Java语言系统中支持以下4种类加载器:
Bootstrap ClassLoader (启动类加载器)
默认加载的是jdk\lib目录下jar中诸多类;
这个路径可以使用 -Xbootclasspath参数指定。
Extension ClassLoader (扩展类加载器)
默认加载jdk\lib\ext\目录下jar中诸多类;
这个路径可以使用 java.ext.dirs系统变量来更改。
Application ClassLoader (应用程序类加载器)
负责加载开发人员所编写的诸多类。
User ClassLoader (自定义类加载器)
当存在上述类加载器解决不了的特殊情况,或存在特殊要求时,可以自行实现类加载逻辑。
关系如图所示:
什么是双亲委派
其是在JDK1.2期间被引入的,而后陆续被推荐给开发者,到目前已经成为了最常用的类加载器实现方式了。 双亲委派整个过程分为以下几步:
-
假设用户刚刚摸鱼写的Test类想进行加载,这个时候首先会发送给应用程序类加载器AppCloassLoader;
-
然后AppClassLoader并不会直接去加载Test类,而是会委派于父类加载器完成此操作,也就是ExtClassLoader;
-
ExtClassLoader同样也不会直接去加载Test类,而是会继续委派于父类加载器完成,也就是BootstrapClassLoader;
-
BootstrapClassLoader这个时候已经到顶层了,没有父类加载器了,所以BootstrapClassLoader会在jdk/lib目录下去搜索是否存在,因为这里是用户自己写的Test类,是不会存在于jdk下的,所以这个时候会给子类加载器一个反馈。
-
ExtClassLoader收到父类加载器发送的反馈,知道了父类加载器并没有找到对应的类,爸爸靠不住,就只能自己来加载了,结果显而易见,自己也不行,没办法,只能给更下面的子类加载器了。
AppClassLoader收到父类加载器的反馈,顿时明白,原来爸爸虽然是爸爸,但是他终究不能管儿子的私事,所以这时候,AppClassLoader就自己尝试去加载。
总结:就是一个类从应用类加载器(Application ClassLoader)进入,会不断的向上找类加载器直到找到类加载器的顶层->启动类加载器(BootstrapClassLoader),然后查看有没有对应的类,如果没有就交给子类去加载。
为什么需要双亲委派机制
使用双亲委派模型,有一个很大的好处,就是避免原始类被覆盖的问题。
比如,用户编写了一个Object类,放入程序中加载。
当没有双亲委派机制时,就会出现重复的Object类,会给开发人员造成很大的困扰,本来就只需要基于JDK开发就好了,现在还得把JDK中的类全记住,避免编写重复的类。
当存在双亲委派机制时呢,整个事情就不一样了,每次加载类时,都会遵循双亲委派机制,去问父类是否可以加载,如果可以呢,那就不需要再次加载了,这样事情就变得简单了。(老子走的路,小子不能走 )
另外,通过双亲委派的方式,还保证了安全性。因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如java.lang.Integer,那么这个类是不会被随意替换的,除非有人跑到你的机器上, 破坏你的JDK。
那么,就可以避免有人自定义一个有破坏功能的java.lang.Integer被加载。这样可以有效的防止核心Java API被篡改。
总结:1.避免类的重复加载
2.防止核心类被篡改
如何主动破坏双亲委派机制?
首先看一下loadclass的源码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//首先检查类是否已经被加载过了
Class<?> c = findLoadedClass(name);
//若父加载器为空则默认使用启动类加载器作为父加载器
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
看过源码我们知道了双亲委派模型的实现,那么想要破坏双亲委派机制就很简单了。
因为他的双亲委派过程都是在loadClass方法中实现的,那么想要破坏这种机制,那么就自定义一个类加载器,重写其中的loadClass方法,使其不进行双亲委派即可。
总结:重写loadclass方法
双亲委派被破坏的例子
-
第一种被破坏的情况是在双亲委派出现之前。
由于双亲委派模型是在JDK1.2之后才被引入的,而在这之前已经有用户自定义类加载器在用了。所以,这些是没有遵守双亲委派原则的。 -
第二种,是JNDI、JDBC等需要加载SPI接口实现类的情况。
-
第三种是为了实现热插拔热部署工具。为了让代码动态生效而无需重启,实现方式时把模块连同类加载器一起换掉就实现了代码的热替换。
-
第四种时tomcat等web容器的出现。
-
第五种时OSGI、Jigsaw等模块化技术的应用。