一、热加载时序图
二、热加载原理剖析
开启热加载在<Context标签中配置reloadable="true"
1、StandardEngine容器启动startInternal(),内部会调用父类ContainerBase的startInternal()
protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if(log.isInfoEnabled())
log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
// Standard container startup
// 调用ContainerBase的startInternal,以异步的方式启动子容器
super.startInternal();
}
2、进入ContainerBase startInternal(),注意方法最后threadStart()中会开启一个后台线程
protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
// 启动下级组件,如果有的话
// 容器的类加载器
Loader loader = getLoaderInternal();
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();
/*此处移除部分代码
*/
//开启管道、也是层层调用开启
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
// 这个时候会触发START_EVENT事件,会进行deployApps
setState(LifecycleState.STARTING);
// Start our thread
// Engine容器启动一个background线程
threadStart();
}
3、进入threadStart()
protected void threadStart() {
if (thread != null)
return;
// System.out.println(this.getInfo() + "的backgroundProcessorDelay等于=" + backgroundProcessorDelay);
// 默认情况下只有Engine的backgroundProcessorDelay大于0,为10,
// 也就是说,虽然每个容器在启动的时候都会走到当前方法,但是只有Engine能继续往下面去执行
// 但是其他容器是可以配置backgroundProcessorDelay属性的,只要配置了大于0,那么这个容器也会单独开启一个backgroundProcessor线程
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
// ContainerBackgroundProcessor线程每隔一段时间会调用容器内的backgroundProcess方法,并且会调用子容器的backgroundProcess方法
thread = new Thread(new ContainerBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
4、进入后台ContainerBackgroundProcessor线程中
threadDone为false,这个后台线程一直会运行,主要作用遍历StandardEngine容器中子容器并调用其backgroundProcess()
public void run() {
Throwable t = null;
String unexpectedDeathMessage = sm.getString(
"containerBase.backgroundProcess.unexpectedThreadDeath",
Thread.currentThread().getName());
try {
while (!threadDone) {
try {
Thread.sleep(backgroundProcessorDelay * 1000L);
} catch (InterruptedException e) {
// Ignore
}
if (!threadDone) {
// 获取当前的容器
Container parent = (Container) getMappingObject();
ClassLoader cl =
Thread.currentThread().getContextClassLoader();
// System.out.println("ContainerBackgroundProcessor在运行"+ parent.getName());
if (parent.getLoader() != null) {
System.out.println(parent.getName() + "有loader");
cl = parent.getLoader().getClassLoader();
}
// 执行子容器的background
processChildren(parent, cl);
}
}
5、进入processChildren(),此方法是父类ContainerBase的即每个容器都会走,每个容器都会调用backgroundProcess(),大致流程engine、host、context、wrapper,总之最终会走到context的backgroundProcess
protected void processChildren(Container container, ClassLoader cl) {
try {
if (container.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(container.getLoader().getClassLoader());
}
//
container.backgroundProcess();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("Exception invoking periodic operation: ", t);
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
Container[] children = container.findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i].getBackgroundProcessorDelay() <= 0) {
// 调用子容器的backgroundProcess方法, delay小于0才调用,如果大于0,则该容器会有自己单独的background线程
processChildren(children[i], cl);
}
}
}
}
6、进入StandardContext的backgroundProcess(),此方法还是会调用父类ContainerBase的backgroundProcess()
public void backgroundProcess() {
//省去多余代码,获取context中webapploader
// 热加载
// 热加载
Loader loader = getLoaderInternal(); // Context.webapploader
if (loader != null) {
try {
loader.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);
}
}
}
7、进入webapploader的backgroundProcess(),可以看到会重新加载Context
public void backgroundProcess() {
if (reloadable && modified()) {
System.out.println(container.getInfo()+"触发了热加载");
try {
Thread.currentThread().setContextClassLoader
(WebappLoader.class.getClassLoader());
if (container instanceof StandardContext) {
//重新reload
((StandardContext) container).reload();
}
} finally {
if (container.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(container.getLoader().getClassLoader());
}
}
} else {
closeJARs(false);
}
}
8、进入StandardContext的reload()、其实主要做了如下四步
public synchronized void reload() {
// Stop accepting requests temporarily.
setPaused(true);//
stop();
start();
setPaused(false);
}
9、进入Context的start()中也就进入类似重新开启context,重新加载context中的类,最终会调用WebAppClassLoader的loadClass()
主要几个步骤:
1、先去tomcat自己维护的map中去找有没有加载
2、再去jvm中找有没有加载
3、先尝试通过系统类加载器去加载(主要是加载jdk核心类)
4、然后根据delegateLoad,是否委托父类去加载,如果没有委托,自己先去加载、加载不到再让父类加载。如果委托,先让父类加载器加载、自己再加载,就是先后顺序
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLockInternal(name)) {
if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class<?> clazz = null;
// Log access to stopped classloader
if (!started) {
try {
throw new IllegalStateException();
} catch (IllegalStateException e) {
log.info(sm.getString("webappClassLoader.stopped", name), e);
}
}
// (0) Check our previously loaded local class cache
// 先检查该类是否已经被Webapp类加载器加载。
clazz = findLoadedClass0(name); // map
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
// 该方法直接调用findLoadedClass0本地方法,findLoadedClass0方法会检查JVM缓存中是否加载过此类
clazz = findLoadedClass(name); // jvm 内存
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 prevent
// the webapp from overriding J2SE classes
// 尝试通过系统类加载器(AppClassLoader)加载类,防止webapp重写JDK中的类
// 假设,webapp想自己去加载一个java.lang.String的类,这是不允许的,必须在这里进行预防。
try {
clazz = j2seClassLoader.loadClass(name); // java.lang.Object
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;
if (name.endsWith("BeanInfo")) {
// BZ 57906: suppress logging for calls from
// java.beans.Introspector.findExplicitBeanInfo()
log.debug(error, se);
} else {
log.info(error, se);
}
throw new ClassNotFoundException(error, se);
}
}
}
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
}
}
// (2) Search local repositories
// 从webapp应用内部进行加载
if (log.isDebugEnabled())
log.debug(" Searching local repositories");
try {
clazz = findClass(name); // classes,lib
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
// 如果webapp应用内部没有加载到类,那么无条件委托给父类进行加载
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);
}