Tomcat classloader
tomcat类加载器结构
-
Bootstrap ClassLoader 启动类加载器
加载Java核心库(JAVA_HOME/jre/lib
)到JVM中.用c/c++实现.是JVM的一部分, JVM一启动就将指定的类加载到内存中,避免以后多次I/O.Java程序无法直接使用它 -
Extension ClassLoader 拓展类加载器
加载Java拓展库(JAVA_HOME/jre/lib/ext
).该类(ExtensionClassLoader)使用BootstrapClassLoader加载 -
SystemClassLoader 系统类加载器 (又名 程序类加载器 ApplicationClassLoader)
加载用户类路径(CLASSPATH)指定的类库
,若程序没有自己定义类加载器, 就默认使用 系统类加载器. 它也由BootstrapClassLoader加载,但它的父加载器被设置成了ExtensionClassLoader.
它负责加载应用程序的类,包括自己写的和引入的第三方法类库,即所有在类路径中指定的类。
负责加载用户类路径(-cp参数)指定的类库 -
Common ClassLoader
commonClassLoader 负责加载 tomcat 应用服务器内部 和 web应用 均可见的类.
如Servlet规范相关包 和 一些通用的工具包. tomcat应用服务器 顶层 公共类加载器,路径为 common.loader,默认指向$CATALINA_BASE/lib 和 $CATALINA_HOME/lib
下的包.
默认情况下 CommonClassLoader,CatalinaClassLoader,SharedClassLoader 是同一个, 均为common.但是可配置创建3个不同的类加载器,使他们各司其职 -
Catalina ClassLoader
CatalinaClassLoader 加载 只有tomcat应用服务器内部 可见的类,这些类对web应用不可见.
如tomcat的具体实现类 加载Tomcat应用服务的 类加载器 路径 server.loader,默认为空. 此时tomcat使用commonClassLoader作为web应用的父加载器 -
Shared ClassLoader
SharedClassLoader 负责加载web应用共享的类,这些类tomcat不会依赖.
是所有web应用的父加载器,路径为 shared.loader,默认为空. 此时tomcat使用CommonLoader作为 web应用的父加载器 -
WebApp1 ClassLoader
加载/WEB-INF/classes
目录下的未压缩的Class和资源文件,以及/WEB-INF/lib
下的Jar包.该类加载器 只对当前Web应用可见, 对其他web应用均不可见.
Tomcat7 默认使用 WebappClassLoader 类加载器. 而 Tomcat8 默认使用 ParallelWebappClassLoader,支持并行加载类的特性. -
WebApp2 ClassLoader
其它web应用类加载器…
创建 CommonClassLoader
Bootstrap.init() 时会实例化类加载器.
在初始化类加载器之后立即 设置 线程上下文类加载器(Thread Context ClassLoader)为 catalina 类加载器
为 Catalina 组件指定父类加载器
org.apache.catalina.startup.Bootstrap#initClassLoaders
/** 初始化类加载器 */
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null); // 创建一个 Common 类加载器
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}// 默认catalinaLoader和sharedLoader都使用父类加载器commonLoader
catalinaLoader = createClassLoader("server", commonLoader); // 再根据 Common 类加载器创建 server 类加载器
sharedLoader = createClassLoader("shared", commonLoader); // 再根据 Common 类加载器创建 shared 类加载器
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
WebappClassLoaderBase 继承结构
ClassLoader (java.lang)
SecureClassLoader (java.security)
URLClassLoader (java.net)
WebappClassLoaderBase (org.apache.catalina.loader)
ParallelWebappClassLoader (org.apache.catalina.loader)
WebappClassLoader (org.apache.catalina.loader)
WebappLoader
WebappClassLoaderBase 实现了主要的逻辑,并且继承了 Lifecycle,在 tomcat 组件启动、关闭时会完成资源的加载、卸载操作.
例如在 start 过程会读取 /WEB-INF/classes
、/WEB-INF/lib
资源,并且记录每个 jar 包的时间戳方便重载 jar 包.
在组件 stop 的时候,会清理已经加载的资源;
destory 时会显式地触发 URLClassLoader.close()
WebappLoader 持有 WebappClassLoaderBase.
WebappClassLoaderBase 的具体实现包括 ParallelWebappClassLoader 和 WebappClassLoader.
初始化 WebappLoader
在 Tomcat 中,每个 webapp 对应一个 StandardContext,在 start 过程便会实例化 WebappLoader,并且调用其 start() 方法完成初始化,包括创建 ParallelWebappClassLoader 实例,然后,还会启动 Context 的子容器。
这两个过程,都会将线程上下文类加载器指定为 ParallelWebappClassLoader 类加载器,在完成 webapp 相关的类加载之后,又将线程上下文类加载器设置为 catalina 类加载器。
StandardContext 启动时 会 实例化 WebappLoader
ClassLoader oldCCL = bindThread();
if (getLoader() == null) {
// 创建 WebappLoader,每个web应用都使用各自的 WebappClassLoader
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
// ...
unbindThread(oldCCL);
// Start our subordinate components, if any
// 启动 WebappLoader
Loader loader = getLoaderInternal();
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();// 启动web应用类加载器(WebappLoader)
WebappLoader 用于管理 WebappClassLoader
WebappLoader 在 startInternal() 中 创建 WebappClassLoader
WebappLoader 提供 backgroundProcess() 用于 Context 后台处理.当检测 web 应用的类文件,jar 包发生变更时,重新加载 Context.
WebappLoader 在 stop 时,会 销毁 WebappClassLoader
tomcat类加载顺序
-
默认加载顺序,delegate=false 即不启用java委派模式
- 1.从缓存中加载
- 2.若无,则从 BootstrapClassLoader 加载(保证Java核心类库不会被覆盖)
- 3.若无,则从当前类加载器加载(即 WebAppClassLoader,按照 WEB-INF/classes, WEB-INF/lib 顺序)
- 4.若无,则从父类加载器加载,由于父类加载器 采用默认的委派机制,则加载顺序为 System -> Common -> Shared (从父到子)
-
配置 delegate=true 即启用java委派模式
- 1.从缓存中加载
- 2.若无,则从 BootstrapClassLoader 加载
- 3.若无,则从父类加载器加载,加载顺序为 System -> Common -> Shared (从父到子)
- 4.若无,则从当前类加载器加载(即 WebAppClassLoader,按照 WEB-INF/classes, WEB-INF/lib 顺序)
-
JVM endorsed机制
-
定义
- Endorsed Standards Override Mechanism 机制用于 允许替换JCP之外生成的api,该机制允许应用程序 提供新版本的api来覆盖JVM的默认实现.
默认目录%JAVA_HOME%/lib/endorsed
,可通过启动参数java.endorsed.dir来修改.该目录下的jar包优先级高于jvm中的类加载.(但是规定只有部分类库被允许替换)
- Endorsed Standards Override Mechanism 机制用于 允许替换JCP之外生成的api,该机制允许应用程序 提供新版本的api来覆盖JVM的默认实现.
-
实例
- web应用通过endorsed加载新的JAXP包,甚至核心类库(rt.jar)
逻辑再 org.apache.catalina.loader.WebappClassLoaderBase#loadClass(java.lang.String, boolean) 里
// (0)Check our previously loaded local class cache.检查 WebappClassLoader 缓存中是否加载过此类. resourceEntries
clazz = findLoadedClass0(name);
// (0.1) Check our previously loaded class cache 2.检查 JVM 虚拟机中是否加载过该类.调用 java.lang.ClassLoader 中的 native 方法
clazz = findLoadedClass(name);
// (0.2) Try loading the class with the system class loader, to prevent the webapp from overriding J2SE classes
// 使用 ExtClassLoader 加载该类(当然它也会委托它的父类加载器 BootstrapClassLoader),防止覆盖 J2SE 类库
clazz = j2seClassLoader.loadClass(name);
// (0.5) Permission to access this class when using a SecurityManager
// (1) Delegate to our parent if requested.
// 若 delegate 设置为 true,则会完全按照 JVM 的双亲委托机制流程加载类.委托给parent,即 CommonClassLoader,它会继续向上委托.
if (delegateLoad) {
// parent为 CommonClassLoader
clazz = Class.forName(name, false, parent);
}
// (2) Search local repositories
// 使用 WebappClassLoader.findClass() 加载类
clazz = findClass(name);
clazz = findClassInternal(name);
entry = findResourceInternal(name, path, true); // 1.通过名称去当前 webappClassLoader 的仓库中查找对应的类文件
clazz = defineClass() // 2.将找到的类文件通过 defineClass() 转变为 Jvm 可以识别的 Class 对象返回
// (3) Delegate to parent unconditionally 6.若还是没找到,则委托给父类加载器加载
clazz = Class.forName(name, false, parent);
热加载
当 context 配置了 reloadable="true"
,WebappLoader 会启动 backgroundProcess(),它用于监听 class、jar 等文件的变更,一旦有变动,便会对 Context 容器进行 reload 操作.
通过 WebappLoader#setContext(Context context) 方法调用,从而传递给 WebappLoader.
<Context path="examples" docBase="/tomcat/webapps/examples" reloadable="true" />
org.apache.catalina.loader.WebappLoader#backgroundProcess
public void backgroundProcess() {
if (reloadable && modified()) {
try {
Thread.currentThread().setContextClassLoader
(WebappLoader.class.getClassLoader()); // 变更线程上下文类加载器为 webapp 类加载器
if (container instanceof StandardContext) {
((StandardContext) container).reload(); // 重载 webapp
}
} finally {
if (container.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(container.getLoader().getClassLoader());
}
}
} else {
closeJARs(false);
}
}