我们都知道虚拟机在加载类的过程中是使用类加载器进行加载,Java中类加载器有多种,当JVM加载一个.class文件的时候,是由哪个类加载器加载呢?
双亲委派机制
当一个类加载器收到类加载的请求时,不会直接去加载指定的类,
而是委托给自己的父加载器去加载(层级关系,不是继承哦),
只有父加载器无法加载类时,才会由当前类加载器来负责加载
Java支持4种类加载器:
-
Bootstrap ClassLoader 启动类加载器
-
Extention ClassLoader 标准扩展类加载器
-
Application ClassLoader 应用类加载器
-
User ClassLoader 用户自定义类加载器
-
Bootstrap ClassLoader ,主要负责加载Java核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
-
Extention ClassLoader,主要负责加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。
-
Application ClassLoader ,主要负责加载当前应用的classpath下的所有类
-
User ClassLoader , 用户自定义的类加载器,可加载指定路径的class文件
加载流程:
-
子类先委托父类加载
-
父类加载器有自己的加载范围,范围内没有找到,则不加载,并返回给子类
-
子类在收到父类无法加载的时候,才会自己去加载
类加载器加载范围:
-
启动类加载器(Bootstrap ClassLoader)
C++实现,负责加载<JAVA_HOME>/lib下的类
-
扩展类加载器(Extension ClassLoader)
Java实现,负责加载<JAVA_HOME>/lib/ext下的类
-
系统类加载器/应用程序类加载器(Application ClassLoader)
代码默认由它来加载,ClassLoader.getSystemClassLoader返回的就是它
-
public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkClassLoaderPermission(scl, Reflection.getCallerClass()); } return scl; }
为什么需要双亲委派?
优点:
-
可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类
-
保证了安全性,因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,类不会被随意修改替换,防止JDK被篡改
双亲委派的实现
java.lang.ClassLoader.java
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 {
//bootstrap classloader的类加载器为null,则通过find方法来获得
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 如果仍没有获得该类,调用findClass找到类
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方法中实现的,破坏双亲委派机制只需要定义类加载器
自定义类加载器
-
遵守双亲委派机制
继承ClassLoader,重写findClass()方法。 -
破坏双亲委派机制
继承ClassLoader,重写loadClass()方法。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
破坏双亲委派机制
-
jdbc
原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,由不同数据库类型去实现。例如,MySQL的mysql-connector-java.jar中的Driver类具体实现的。 原生的JDBC中的类是放在rt.jar包的,是由启动类加载器进行类加载的,在JDBC中的Driver类中需要动态去加载不同数据库类型的Driver类,而mysql-connector-java.jar中的Driver类是用户自定义的,那启动类无法进行加载,需要由应用程序启动类去进行类加载。
Class mysql = Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println(mysql.getClassLoader());
JDK 自己为解决该问题,引入线程上下文类加载器,可以通过Thread的setContextClassLoader()进行设置
-
Tomcat要破坏双亲委派
Tomcat破坏双亲委派原则,提供隔离的机制,为每个web容器单独提供一个WebAppClassLoader加载器。Tomcat的类加载机制:为了实现隔离性,优先加载 Web 应用自己定义的类,所以没有遵照双亲委派的约定,每一个应用自己的类加载器——WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反。