浅析Tomcat
源码下载
- 下载Tomcat (官网:Tomcat)
- 解压源码包(如下图),并在该位置创建文件夹,命名为:catalina-home
{# 你的安装地址}\tomcat\apache-tomcat-7.0.70-src_源码\apache-tomcat-7.0.70-src
3. 把tomcat源码包中的 conf文件夹 复制到 catalina-home 目录下
注意:catalina-home文件夹就是也tomcat实例的运行环境
Tomcat的类加载机制
- 引导类加载器 和 扩展类加载器 的作⽤不变;
- 系统类加载器正常情况下加载的是 CLASSPATH 下的类,但是 Tomcat 的启动脚本并未使⽤该变量,⽽是加载tomcat启动的类,⽐如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下;
- Common 通⽤类加载器加载Tomcat使⽤以及应⽤通⽤的⼀些类,位于CATALINA_HOME/lib下,⽐如servlet-api.jar;
- Catalina ClassLoader ⽤于加载服务器内部可⻅类,这些类应⽤程序不能访问;
- SharedClassLoader ⽤于加载应⽤程序共享类,这些类服务器不会依赖;
- WebappClassLoader,每个应⽤程序都会有⼀个独⼀⽆⼆的Webapp ClassLoader,他⽤来加载本应⽤程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类。
需要注意:
tomcat 8.5 默认改变了严格的双亲委派机制: - 从缓存中加载;
- 如果缓存中没有,会先调用ExtClassLoader进行加载, 扩展类加载器是遵循双亲委派的,他会调用bootstrap,查看对应的lib有没有,然后回退给ExtClassLoader对扩展包下的数据进行加载;
- 如果未加载到,则从 /WEB-INF/classes加载;
- 如果未加载到,则从 /WEB-INF/lib/*.jar 加载如果未加载到,WebAppclassLoader 会委派给SharedClassLoader,SharedClassLoad会委派给CommonClassLoader…,依次委派给BootstrapClassLoader, 然后BootstrapClassLoader 在自己目录中查找对应的类如果有则进行加载,如果没有他会委派给下一级ExtClassLoader,ExtClassLoader再查找自己目录下的类,如果有则加载如果没有则委派给下一级……遵循双亲委派原则。
分析应用类加载器的加载过程
应用类加载器为WebappClassLoader ,他的loadClass在他的父类WebappClassLoaderBase中。
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class<?> clazz = null;
// Log access to stopped class loader
checkStateForClassLoading(name);
//从当前ClassLoader的本地缓存中加载类,如果找到则返回
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
// 本地缓存没有的情况下,调用ClassLoader的findLoadedClass方法查看jvm是否已经加载过此类,如果已经加载则直接返回。
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
String resourceName = binaryNameToPath(name, false);
//此时的javaseClassLoader是扩展类加载器 是把扩展类加载器赋值给了javaseClassLoader
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
.....
//如果可以用getResource得到
//如果能用扩展类加载器的getResource得到就证明可以被扩展类加载器加载到接下来安排扩展类加载器加载
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 = "Security Violation, attempt to use " +
"Restricted Class: " + name;
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
boolean delegateLoad = delegate || filter(name, true);
// (1) Delegate to our parent if requested
//如果是true就是用父类加载器进行加载
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
}
}
// (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: " + 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
}
}
}
throw new ClassNotFoundException(name);
}
在37行英文注释中标注获取的是系统类加载器,但我们debug的时候会发现他是扩展类加载器,实际中我们可以推断出他应该是扩展类加载器,因为如果我们加载的类在扩展类加载器路径下已经存在的话,那我们直接调用系统类加载器是就是错误的了,下图为debug后获取的类加载器的验证。
tomcat打破了双亲委派的原则,实际是在应用类加载器中打破了双亲委派,其他类加载器还是遵循双亲委派的。
指定idea控制台中的编码格式
在idea64.exe.vmoptions文件中,添加如下的配置
##IDEA的启动内存,越大越好。
-Xms3880m
##IDEA的最大运行内存,也是越大越好。
-Xmx4096m
##IDEA保留代码可占用的内存
-XX:ReservedCodeCacheSize=1024m
##指定idea中文件的编码格式,可以解决idea控制台日志乱码的问题。
-Dfile.encoding=UTF-8