文章目录
- 类加载器
- 双亲委派机制
- 双亲委派的好处
- Tomcat的类加载器
- loadClass总体加载步骤:
类加载器
三种JDK内部的类加载器
- 启动类加载器(BootStrap ClassLoader)
负责加载JRE\lib下的rt.jar、resources.jar、charsets.jar包中的class
。
- 扩展类加载器(Extension ClassLoader)
负责加载jre\lib\ext\
目录下面的jar包。
- 应用类加载器(Application ClassLoader)
应用类加载器就是负责加载应用路径(classpath
)上的类了。
具体可参考下图:
双亲委派机制
双亲委派机制指的是,当收到一个类加载请求后,ClassLoader
首先不会自己直接去加载这个类,而是委派给父类去加载,只有当父类加载器在其路径下没有找到所需要加载的类之后,子类加载器才会自己尝试去加载。
双亲委派的好处
采用双亲委派机制可以避免类的重复加载,以及一些需要保护的类,不会被篡改,比如我们要加载rt.jar
包中的java.lang.Object
类,不论是哪个类加载器,最终一定要交给启动类加载器(BootstrapClassLoader
)来加载,而启动类加载器就负责加载像rt.jar
这样的包中的类,从而也保证了最终系统中只有一个Object
类。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 先看看有没有已经加载过了
Class<?> c = findLoadedClass(name);
// 如果没有加载过
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果parent为空,则表示已经交给了Bootstrap类加载器了
if (parent != null) {
// 通过递归的方式,让父类加载器去加载
c = parent.loadClass(name, false);
} else {
// 通过bootstrap类加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 如果父类加载器都没有加载到,就使用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;
}
}
// 通过重写findClass方法实现自定义加载方式
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
Tomcat的类加载器
可以看出JVM的双亲委派加载机制关键实现就在loadClass和findClass
这个方法中,所以如果要打破双亲委派机制关键就要重写loadClass和findClass
这两个方法。
我们先来看看loadClass
方法
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(n
if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class<?> clazz = null;
// Log access to stopped class loader
checkStateForClassLoading(name);
// (0) Check our previously loaded local class cache
// 先在本地缓存中查找是否已经加载过了
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
// (0.1) Check our previously loaded class cache
// 先在本地缓存中查找是否已经加载过了
clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
// (0.2) Try loading the class with the system class loader, to preve
// the webapp from overriding Java SE classes. This implements
// SRV.10.7.2
// 在加载webapp目录之前先尝试使用系统类加载器加载(也就是BootstrapClassLoader或者ExtClassLoader)
String resourceName = binaryNameToPath(name, false);
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
// Use getResource as it won't trigger an expensive
// ClassNotFoundException if the resource is not available from
// the Java SE class loader. However (see
// https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
// details) when running under a security manager in rare cases
// this call may trigger a ClassCircularityError.
// See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
// details of how this may trigger a StackOverflowError
// Given these reported errors, catch Throwable to ensure any
// other edge cases are also caught
URL url;
if (securityManager != null) {
PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(re
url = AccessController.doPrivileged(dp);
} else {
url = javaseLoader.getResource(resourceName);
}
tryLoadingFromJavaseLoader = (url != null);
} catch (Throwable t) {
// Swallow all exceptions apart from those that must be re-thrown
ExceptionUtils.handleThrowable(t);
// The getResource() trick won't work for this class. We have to
// try loading it directly and accept that we might get a
// ClassNotFoundException.
tryLoadingFromJavaseLoader = true;
}
if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (0.5) Permission to access this class when using a SecurityManager
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = sm.getString("webappClassLoader.restricted
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
// 如果执行到这还没有找到,说明没有被加载过,且也不是JDK中的类
// 如果delegate为true,则tomcat还是会使用双亲委派加载方式
boolean delegateLoad = delegate || filter(name, true);
// (1) Delegate to our parent if requested
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// delegate默认为false,所以会使用findClass方法加载
// (2) Search local repositories
if (log.isDebugEnabled())
log.debug(" Searching local repositories");
try {
clazz = findClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from local repository");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// (3) Delegate to parent unconditionally
// 最后都没有加载,则再委托给父加载器加载
if (!delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader at end: " + par
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
}
throw new ClassNotFoundException(name);
}
findClass
方法则会优先在Web目录中查找要加载的类
public Class<?> findClass(String name) throws ClassNotFoundException {
if (log.isDebugEnabled())
log.debug(" findClass(" + name + ")");
checkStateForClassLoading(name);
// (1) Permission to define this class when using a SecurityManager
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
if (log.isTraceEnabled())
log.trace(" securityManager.checkPackageDefinition");
securityManager.checkPackageDefinition(name.substring(0,i));
} catch (Exception se) {
if (log.isTraceEnabled())
log.trace(" -->Exception-->ClassNotFoundException", se);
throw new ClassNotFoundException(name, se);
}
}
}
// Ask our superclass to locate this class, if possible
// (throws ClassNotFoundException if it is not found)
Class<?> clazz = null;
try {
if (log.isTraceEnabled())
log.trace(" findClassInternal(" + name + ")");
try {
if (securityManager != null) {
PrivilegedAction<Class<?>> dp =
new PrivilegedFindClassByName(name);
clazz = AccessController.doPrivileged(dp);
} else {
// 先在/WEB-INF/classes目录下查找要加载的类
clazz = findClassInternal(name);
}
} catch(AccessControlException ace) {
log.warn(sm.getString("webappClassLoader.securityException", name,
ace.getMessage()), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
if ((clazz == null) && hasExternalRepositories) {
try {
// 如果找不到则交给父类加载器去查找
clazz = super.findClass(name);
} catch(AccessControlException ace) {
log.warn(sm.getString("webappClassLoader.securityException", name,
ace.getMessage()), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
}
if (clazz == null) {
if (log.isDebugEnabled())
log.debug(" --> Returning ClassNotFoundException");
throw new ClassNotFoundException(name);
}
} catch (ClassNotFoundException e) {
if (log.isTraceEnabled())
log.trace(" --> Passing on ClassNotFoundException");
throw e;
}
// Return the class we have located
if (log.isTraceEnabled())
log.debug(" Returning class " + clazz);
if (log.isTraceEnabled()) {
ClassLoader cl;
if (Globals.IS_SECURITY_ENABLED){
cl = AccessController.doPrivileged(
new PrivilegedGetClassLoader(clazz));
} else {
cl = clazz.getClassLoader();
}
log.debug(" Loaded by " + cl.toString());
}
return clazz;
}
loadClass总体加载步骤:
- 先在cache中查找是否已经加载过,如果有说明Tomcat的类加载器已经加载过了。
- 如果Tomcat没有加载过,则先让系统类加载器加载,也就是
BootstrapClassLoader
或者ExtClassLoader
(优先让系统加载器加载能够保证JDK的核心类不会被覆盖加载)。 - 如果还是没有,则说明不是JDK中的核心类,那么就通过
delegate
属性来决定是继续按照双亲委派方式加载,还是按照自定义的方式加载,也就是调用本地的findClass
方法,去/WEB-INF/classes
目录下查找要加载的类。 - 如果这些都没有找到,则回到双亲委派方式交给父类加载器去查找。
- 最后都没有找到,则抛出ClassNot
在这里插入代码片
Found异常。