Server组件和Service组件是Tomcat核心组件中最外层级的两个组件,Server组件可以看成Tomcat的运行实例的抽象,而Service组件则可以看成Tomcat内的不同服务的抽象。
1. Server
请看:home/conf/server.xml文件
Server组件是代表整个Tomcat的Servlet容器,从server.xml配置文件也可以看出它属于最外层组件。
1.1 Server组件的作用
- 提供了监听器机制,用于在Tomcat整个生命周期中对不同事件进行处理。
- 提供了Tomcat容器全局的命名资源实现。
- 监听某个端口以接收SHUTDOWN命令。
1.2 生命周期监听器Lisenter
默认配置了五个Listener:
GlobalNamingResources:通过JNDI提供统一的命名对象访问接口,它的使用范围是整个Server。
ThreadLocalLeakPreventionListener:在Executor中触发线程更新,线程池被停止,以避免线程本地相关内存泄漏。
关于内存泄漏:引起的内存泄漏问题的根本原因在于当垃圾回收器要回收时无法回收。
举例:线程池中线程的生命周期一般都会比较长,假如Web应用中使用了ThreadLocal保存AA对象,而且AA类由Webappclassloader加载,那么它就可以看成线程引用了AA对象。Web应用重加载是通过重新实例化一个Webappclassloader类加载器来实现的,由于线程一直未销毁,旧的Webappclassloader也无法被回收,导致了内存泄漏。
解决ThreadLocal内存泄漏最彻底的方法就是当Web应用重加载时,把线程池内的所有线程销毁并重新创建,这样就不会发生线程引用某些对象的问题了。
VersionLoggerListener:记录启动时的版本信息。
AprLifecycleListener:将init和销毁APR的LifecycleListener的实现。属于一种优化措施。
JreMemoryLeakPreventionListener:为Java运行时环境可以提供的已知位置提供解决方案,导致内存泄漏或锁定文件。有关JRE内存泄漏的感兴趣可以深入学,这里先带过去。
书上有JasperListener:注意Tomcat8.x是没有初始化这个监听器的。
这也是前面为什么需要将Jsp的解析器下载进去的原因。
1.3 监听SHUTDOWN
ServerSocket组件监听某个端口是否有SHUTDOWN命令,一旦接收到则关闭Server,即关闭Tomcat。
当Tomcat启动时,Server将被主线程执行,其实就是完成所有的启动工作,包括启动接收客户端和处理客户端报文的线程,这些线程都是daemon线程。所有启动工作完成后,主线程将进入等待SHUTDOWN命令的环节,它将不断尝试读取客户端发送过来的消息,一旦匹配SHUTDOWN命令则跳出循环。主线程继续往下执行Tomcat的关闭工作。最后主线程结束,整个Tomcat停止。
org.apache.catalina.startup.Catalina#start
/**
启动一个新的服务器实例。
*/
public void start() {
//。。。启动代码
//注册关闭钩子函数
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
//如果JULI被使用,禁用JULI的关机钩子
//关闭钩子并行运行,日志消息可能丢失
//如果JULI的钩子在CatalinaShutdownHook()之前完成
//打印日志代码
if (await) {
await();
stop();
}
}
StandardServer 的生命周期有关的方法有:initialize,start,stop 和 await。
与其他组件类似,initialize 用于初始化,start 用于启动,然后调用 await 等待关闭命令,最后,调用 stop 关闭Server。调用 await 方法后,server 会被阻塞,直到总 8085 端口(或其他端口,自定)收到了关闭命令。当await 命令返回后,stop 方法会关闭所有的子组件。
start 方法只能调用一次,再次调用会抛出异常。
await 方法负责等待关闭整个 tomcat 系统的命令。
1.4 全局命名资源
在Tomcat启动初始化时,通过Digester框架将server.xml的描述映射到对象,在Server组件中创建NamingResources和NamingContextListener两个对象。监听器将在启动初始化时利用ContextResources里面的属性创建命名上下文,并且组织成树状。
2. Service
/*
* A Service是一个或多个服务的组
* Connectors共享一个Container
*处理传入的请求。这种安排允许,例如,
*非SSL和SSL连接器共享相同数量的web应用程序。
*给定的JVM可以包含任意数量的服务实例;
然而,他们是彼此完全独立,只共享基本的JVM设施和类在系统类路径上。
*/
public interface Service extends Lifecycle {
Service组件是若干Connector组件和Executor组件组合而成的概念。
Connector组件负责监听某端口的客户端请求,不同的端口对应不同的Connector。Executor组件在Service抽象层面提供了线程池,让Service下的组件可以共用线程池。默认情况下,不同的Connector组件会自己创建线程池来使用,而通过Service组件下的Executor组件则可以实现线程池共享,每个Connector组件都使用Service组件下的线程池。
其在Tomcat内的标准实现为:org.apache.catalina.core.StandardService
其内的主要字段为:
/* *
*拥有该服务的服务器,如果有的话。
*/
private Server server = null;
/* *
*此组件的属性更改支持。
*/
protected final PropertyChangeSupport support = new PropertyChangeSupport(this);
/**
与此服务关联的连接器集。
*/
protected Connector connectors[] = new Connector[0];
private final Object connectorsLock = new Object();
由以上字段可以看出:container 只有一个,connector 可以有
多个。
其中的addConnector 方法和 removeConenctor 方法分别用于添加/删除 connector 到 service 中。
@Override
public void addConnector(Connector connector) {
synchronized (connectorsLock) {
connector.setService(this);
Connector results[] = new Connector[connectors.length + 1];
System.arraycopy(connectors, 0, results, 0, connectors.length);
results[connectors.length] = connector;
connectors = results;
if (getState().isAvailable()) {
try {
connector.start();
} catch (LifecycleException e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
// Report this property change to interested listeners
support.firePropertyChange("connector", null, connector);
}
}
@Override
public void removeConnector(Connector connector) {
synchronized (connectorsLock) {
int j = -1;
for (int i = 0; i < connectors.length; i++) {
if (connector == connectors[i]) {
j = i;
break;
}
}
if (j < 0)
return;
if (connectors[j].getState().isAvailable()) {
try {
connectors[j].stop();
} catch (LifecycleException e) {
log.error(sm.getString(
"standardService.connector.stopFailed",
connectors[j]), e);
}
}
connector.setService(null);
int k = 0;
Connector results[] = new Connector[connectors.length - 1];
for (int i = 0; i < connectors.length; i++) {
if (i != j)
results[k++] = connectors[i];
}
connectors = results;
// Report this property change to interested listeners
support.firePropertyChange("connector", connector, null);
}
}
这两个方法就在加入和移除Connector时就启动与停止Connector。
2.1 Lifecycle
再来看其继承的父类,LifecycleMBeanBase:
public abstract class LifecycleMBeanBase extends LifecycleBase
implements JmxEnabled {
public class StandardService extends LifecycleMBeanBase implements Service {
我们可以看到其生命周期函数都是重载的父类的函数,很容易就可以猜到这里十有八九就有一个模板设计模式。但是注意这两个类的重写方式。
org.apache.catalina.core.StandardService#initInternal,destroyInternal
/**
*调用启动前的初始化。这用于允许连接器
*在Unix操作环境下绑定到受限制的端口。
*/
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
if (engine != null) {
engine.init();
}
// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
然后显示调用其父类的方法。
接着往上找一个父类:LifecycleBase,再这个类里我们可以很清楚的看到其是一个模板方法,这就验证了我们之前的猜想。
org.apache.catalina.util.LifecycleBase#start
/**
*子类必须确保状态被更改为
* {@link LifecycleState# started}在这个方法的执行期间。
*改变状态将触发{@link生命周期#START_EVENT}事件。
*
*如果一个组件无法启动,它可能抛出一个
* {@link LifecycleException},这将导致它的父程序启动失败
*或者它可以将自己置于错误状态,在这种情况下{@link #stop()}
*将对失败的组件调用,但父组件将调用
*继续正常启动。
*
* @throw LifecycleException启动错误occu
*/
protected abstract void startInternal() throws LifecycleException;
这里处于先了解,其作用与调用方式我们看到后面就知道了。