Tomcat源码分析---后台线程管理
Tomcat后台管理以前也接触过,比如对普通session,持久session,集群session的超时管理,就是后台线程去做的。
在看时序图之前首先看一下后台线程,也就是ContainerBase的内部类ContainerBackgroundProcessor里面的一个重要方法
processChildren(),它是由ContainerBackgroundProcessor#run()每隔一定的时间调用的。
processChildren()方法内容如下:
protected void processChildren(Container container, ClassLoader cl) {
try {
if (container.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(container.getLoader().getClassLoader());
}
container.backgroundProcess();
} catch (Throwable 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) {
processChildren(children[i], cl);
}
}
}
实际上它就是调用当前容器的backgroundProcess(),然后再递归执行每个子容器,再调用子容器的backgroundProcess()。
下面来看看ContainerBase#backgroundProcess():
public void backgroundProcess() {
if (!started)
return;
if (cluster != null) {
try {
cluster.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e);
}
}
if (loader != null) {
try {
loader.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);
}
}
if (manager != null) {
try {
manager.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.manager", manager), e);
}
}
if (realm != null) {
try {
realm.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e);
}
}
Valve current = pipeline.getFirst();
while (current != null) {
try {
current.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e);
}
current = current.getNext();
}
lifecycle.fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
}
这段代码的作用就是调用容器相关联的Cluster,Loader,Manager,Realm的后台方法,再调用和它管理的所有valve,最后触发一个监听事件。这两段代码就是后台管理的核心了。Tomcat对后台管理就是像一个倒着的树一样,一步一步执行下来的。
下面进入正题,看看后台管理调用的时序图:

首先是从CatalinaShutdownHook开始,先调用自己的processChildren(),然后调用第一个容器StandardEngine,引擎会执行和它相关联的Cluster,Loader,Manager,Realm的后台方法,这里只关联了Realm,所以执行Realm的后台方法,这不很简单,一个空方法,所以时序图省略了。然后引擎开始调用它的所有子容器,就是StandardHost,和主机关联的组件没有,所以不执行,valve也是空方法,不要以为这里就完事了,这里会触发一个监听事件,所以会调用到HostConfig,这里可能就会触发重新部署了。
HostConfing首先调用一个checkResources(),这主要是对部署描述文件进行检查,检查他们的时间戳,看看是否改变了,如果改变的话,就将当前的上下文停止掉,并且从自己的部署列表中删除这个上下文。注意,因为这里会有一个重部署的检查工作,所以不会简单的停止启动就完事了,后面还有检查工作。停止完后,再调用自身的deployApps(),这个方法就是启动时候调用的部署方法,里面会调用三种部署应用,这里不是把每个应用都再部署一遍,如果发现某个应用不在自己的列表里,就会执行一次部署工作,这个部署工作就和启动时的部署一模一样了,所以这里就省略了,具体请看《Tomcat源码分析---启动过程》一文。
主机的后台方法执行完了,会回到ContainerBackgroundProcessor,接着执行主机下面的子容器,现在执行的就是StandardContext,它会调用WebappLoader#backgroundProcess(),这个后台方面里会检查当前应用的class和jar是否改动过,这个过程是委托给WebappClassLoader#modified(),这个modified()方法会检查class和jar的时间戳,获取时间戳又会委托FileDirContext#getAttributes(),如果发现有class或者jar改动过了,就简单的重启上下文就可以了,因为部署描述符还是一样的,只是类和jar更新了,所以重启上下文就可以了。
接着上下文会执行ManagerBase#backgroundProcess(),它会调用StandardSession#isValid(),如果session检查自己已经失效就会调用自定义的session属性的Listener,同时将自己从ManagerBase中删除,我们以前所介绍的session管理,其中的后台管理就和这里的步骤完全一样。
上下文执行完了,回到ContainerBackgroundProcessor,此后会继续执行StandardWrapper#backgroundProcess(),StandardWrapper会先调用父类ContainerBase#backgroundProcess(),由于StandardWrapper没有和其他的组件管理,也没有事件监听器,valve的后台方法都是空的,所以返回,此时StandardWrapper会调用这么一段:
if (getServlet() != null && (getServlet() instanceof PeriodicEventListener)) {
((PeriodicEventListener) getServlet()).periodicEvent();
}
谁继承了这个监听器呢?只有JspServlet继承了,这个periodicEvent()会调用checkCompile()方法,默认情况下JSP预编译是没有打开的,如果打开了JSP预编译,在启动后我们又部署了一个新的JSP到应用上,JspServlet就会检查到,然后在后台编译这个JSP。
包装执行完了,后台所有的东西就介绍了,从这里可以看出,Tomcat后台检查实际上比是较繁重的。
在看时序图之前首先看一下后台线程,也就是ContainerBase的内部类ContainerBackgroundProcessor里面的一个重要方法
processChildren(),它是由ContainerBackgroundProcessor#run()每隔一定的时间调用的。
processChildren()方法内容如下:
protected void processChildren(Container container, ClassLoader cl) {
try {
if (container.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(container.getLoader().getClassLoader());
}
container.backgroundProcess();
} catch (Throwable 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) {
processChildren(children[i], cl);
}
}
}
实际上它就是调用当前容器的backgroundProcess(),然后再递归执行每个子容器,再调用子容器的backgroundProcess()。
下面来看看ContainerBase#backgroundProcess():
public void backgroundProcess() {
if (!started)
return;
if (cluster != null) {
try {
cluster.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e);
}
}
if (loader != null) {
try {
loader.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);
}
}
if (manager != null) {
try {
manager.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.manager", manager), e);
}
}
if (realm != null) {
try {
realm.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e);
}
}
Valve current = pipeline.getFirst();
while (current != null) {
try {
current.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e);
}
current = current.getNext();
}
lifecycle.fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
}
这段代码的作用就是调用容器相关联的Cluster,Loader,Manager,Realm的后台方法,再调用和它管理的所有valve,最后触发一个监听事件。这两段代码就是后台管理的核心了。Tomcat对后台管理就是像一个倒着的树一样,一步一步执行下来的。
下面进入正题,看看后台管理调用的时序图:
首先是从CatalinaShutdownHook开始,先调用自己的processChildren(),然后调用第一个容器StandardEngine,引擎会执行和它相关联的Cluster,Loader,Manager,Realm的后台方法,这里只关联了Realm,所以执行Realm的后台方法,这不很简单,一个空方法,所以时序图省略了。然后引擎开始调用它的所有子容器,就是StandardHost,和主机关联的组件没有,所以不执行,valve也是空方法,不要以为这里就完事了,这里会触发一个监听事件,所以会调用到HostConfig,这里可能就会触发重新部署了。
HostConfing首先调用一个checkResources(),这主要是对部署描述文件进行检查,检查他们的时间戳,看看是否改变了,如果改变的话,就将当前的上下文停止掉,并且从自己的部署列表中删除这个上下文。注意,因为这里会有一个重部署的检查工作,所以不会简单的停止启动就完事了,后面还有检查工作。停止完后,再调用自身的deployApps(),这个方法就是启动时候调用的部署方法,里面会调用三种部署应用,这里不是把每个应用都再部署一遍,如果发现某个应用不在自己的列表里,就会执行一次部署工作,这个部署工作就和启动时的部署一模一样了,所以这里就省略了,具体请看《Tomcat源码分析---启动过程》一文。
主机的后台方法执行完了,会回到ContainerBackgroundProcessor,接着执行主机下面的子容器,现在执行的就是StandardContext,它会调用WebappLoader#backgroundProcess(),这个后台方面里会检查当前应用的class和jar是否改动过,这个过程是委托给WebappClassLoader#modified(),这个modified()方法会检查class和jar的时间戳,获取时间戳又会委托FileDirContext#getAttributes(),如果发现有class或者jar改动过了,就简单的重启上下文就可以了,因为部署描述符还是一样的,只是类和jar更新了,所以重启上下文就可以了。
接着上下文会执行ManagerBase#backgroundProcess(),它会调用StandardSession#isValid(),如果session检查自己已经失效就会调用自定义的session属性的Listener,同时将自己从ManagerBase中删除,我们以前所介绍的session管理,其中的后台管理就和这里的步骤完全一样。
上下文执行完了,回到ContainerBackgroundProcessor,此后会继续执行StandardWrapper#backgroundProcess(),StandardWrapper会先调用父类ContainerBase#backgroundProcess(),由于StandardWrapper没有和其他的组件管理,也没有事件监听器,valve的后台方法都是空的,所以返回,此时StandardWrapper会调用这么一段:
if (getServlet() != null && (getServlet() instanceof PeriodicEventListener)) {
((PeriodicEventListener) getServlet()).periodicEvent();
}
谁继承了这个监听器呢?只有JspServlet继承了,这个periodicEvent()会调用checkCompile()方法,默认情况下JSP预编译是没有打开的,如果打开了JSP预编译,在启动后我们又部署了一个新的JSP到应用上,JspServlet就会检查到,然后在后台编译这个JSP。
包装执行完了,后台所有的东西就介绍了,从这里可以看出,Tomcat后台检查实际上比是较繁重的。