之前的几篇文章讲了Tomcat的启动过程,在默认的配置下启动完之后会看到后台实际上总共有6个线程在运行。即1个用户线程,剩下5个为守护线程(下图中的Daemon Thread)。
如果你对什么叫守护线程的概念比较陌生,这里再重复一下:
所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程。这种线程并不属于程序中不可或缺的部分,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。
Tomcat的关闭正是利用了这个原理,即只要将那唯一的一个用户线程关闭,则整个应用就关闭了。
要研究这个用户线程怎么被关闭的得先从这个线程从何产生说起。在前面分析Tomcat的启动时我们是从org.apache.catalina.startup.Bootstrap类的main方法作为入口,该类的453到456行是Tomcat启动时会执行的代码:
前面的文章里分析了daemon.load和daemon.start方法,这里请注意daemon.setAwait(true);这句,它的作用是通过反射调用org.apache.catalina.startup.Catalina类的setAwait(true)方法,最终将Catalina类的实例变量await设值为true。
Catalina类的setAwait方法代码:
/**
* Set flag.
*/
public void setAwait(boolean await)
throws Exception {
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Boolean.TYPE;
Object paramValues[] = new Object[1];
paramValues[0] = Boolean.valueOf(await);
Method method =
catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
method.invoke(catalinaDaemon, paramValues);
}
如前文分析,Tomcat启动时会调用org.apache.catalina.startup.Catalina类的start方法,看下这个方法的代码:
/**
* Start a new server instance.
*/
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
await();
stop();
}
}
前文分析启动时发现通过第19行getServer().start()的这次方法调用,Tomcat接下来会一步步启动所有在配置文件中配置的组件。后面的代码没有分析,这里请关注最后第52到55行,上面说到已经将Catalina类的实例变量await设值为true,所以这里将会执行Catalina类的await方法:
/**
* Await and shutdown.
*/
public void await() {
getServer().await();
}
该方法就一句话,意思是调用org.apache.catalina.core.StandardServer类的await方法:
/**
* Wait until a proper shutdown command is received, then return.
* This keeps the main thread alive - the thread pool listening for http
* connections is daemon threads.
*/
@Override
public void await() {
// Negative values - don't wait on port - tomcat is embedded or we just don't like ports
if( port == -2 ) {
// undocumented yet - for embedding apps that are around, alive.
return;
}
if( port==-1 ) {
try {
awaitThread = Thread.currentThread();
while(!stopAwait) {
try {
Thread.sleep( 10000 );
} catch( InterruptedException ex ) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
}
// Set up a server socket to wait on
try {
awaitSocket = new ServerSocket(port, 1,
InetAddress.getByName(address));
} catch (IOException e) {
log.error("StandardServer.await: create[" + address
+ ":" + port
+ "]: ", e);
return;
}
try {
awaitThread = Thread.currentThread();
// Loop waiting for a connection and a valid command
while (!stopAwait) {
ServerSocket serverSocket = awaitSocket;
if (serverSocket == null) {
break;
}
// Wait for the next connection
Socket socket = null;
StringBuilder command = new StringBuilder();
try {
InputStream stream;
try {
socket = serverSocket.accept();
socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (AccessControlException ace) {
log.warn("StandardServer.accept security exception: "
+ ace.getMessage(), ace);
continue;
} catch (IOException e) {
if (stopAwait) {
// Wait was aborted with socket.close()
break;
}
log.error("StandardServer.await: accept: ", e);
break;
}
// Read a set of characters from the socket
int expected = 1024; // Cut off to avoid DoS attack
while (expected < shutdown.length()) {
if (random == null)
random = new Random();
expected += (random.nextInt() % 1024);
}
while (expected > 0) {
int ch = -1;
try {
ch = stream.read();
} catch (IOException e) {
log.warn("StandardServer.await: read: ", e);
ch = -1;
}
if (ch < 32) // Control character or EOF terminates loop
break;
command.append((char) ch);
expected--;
}
} finally {
// Close the socket now that we are done with it
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// Ignore
}
}
// Match against our command string
boolean match = command.toString().equals(shutdown);
if (match) {
log.info(sm.getString("standardServer.shutdownViaPort"));
break;
} else
log.warn("StandardServer.await: Invalid command '"
+ command.toString() + "' received");
}
} finally {
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null;
// Close the server socket and return
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}
这段代码就不一一分析,总体作用如方法前的注释所说,即“一直等待到接收到一个正确的关闭命令后该方法将会返回。这样会使主线程一直存活——监听http连接的线程池是守护线程”。
熟悉Java的Socket编程的话对这段代码就很容易理解,就是默认地址(地址值由实例变量address定义,默认为localhost)的默认的端口(端口值由实例变量port定义,默认为8005)上监听Socket连接,当发现监听到的连接的输入流中的内容与默认配置的值匹配(该值默认为字符串SHUTDOWN)则跳出循环,该方法返回(第103到107行)。否则该方法会一直循环执行下去。
一般来说该用户主线程会阻塞(第56行)直到有访问localhost:8005的连接出现。
正因为如此才出现开头看见的主线程一直Running的情况,而因为这个线程一直Running,其它守护线程也会一直存在。
说完这个线程的产生,接下来看看这个线程的关闭,按照上面的分析,这个线程提供了一个关闭机制,即只要访问localhost:8005,并且发送一个内容为SHUTDOWN的字符串,就可以关闭它了。
Tomcat正是这么做的,一般来说关闭Tomcat通过执行shutdown.bat或shutdown.sh脚本,关于这段脚本可参照
分析启动脚本那篇文章,机制类似,最终会执行org.apache.catalina.startup.Bootstrap类的main方法,并传入入参"stop",看下本文第2张图片中org.apache.catalina.startup.Bootstrap类的第458行,接着将调用org.apache.catalina.startup.Catalina类stopServer方法:
public void stopServer(String[] arguments) {
if (arguments != null) {
arguments(arguments);
}
Server s = getServer();
if( s == null ) {
// Create and execute our Digester
Digester digester = createStopDigester();
digester.setClassLoader(Thread.currentThread().getContextClassLoader());
File file = configFile();
FileInputStream fis = null;
try {
InputSource is =
new InputSource(file.toURI().toURL().toString());
fis = new FileInputStream(file);
is.setByteStream(fis);
digester.push(this);
digester.parse(is);
} catch (Exception e) {
log.error("Catalina.stop: ", e);
System.exit(1);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// Ignore
}
}
}
} else {
// Server object already present. Must be running as a service
try {
s.stop();
} catch (LifecycleException e) {
log.error("Catalina.stop: ", e);
}
return;
}
// Stop the existing server
s = getServer();
if (s.getPort()>0) {
Socket socket = null;
OutputStream stream = null;
try {
socket = new Socket(s.getAddress(), s.getPort());
stream = socket.getOutputStream();
String shutdown = s.getShutdown();
for (int i = 0; i < shutdown.length(); i++) {
stream.write(shutdown.charAt(i));
}
stream.flush();
} catch (ConnectException ce) {
log.error(sm.getString("catalina.stopServer.connectException",
s.getAddress(),
String.valueOf(s.getPort())));
log.error("Catalina.stop: ", ce);
System.exit(1);
} catch (IOException e) {
log.error("Catalina.stop: ", e);
System.exit(1);
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// Ignore
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
// Ignore
}
}
}
} else {
log.error(sm.getString("catalina.stopServer"));
System.exit(1);
}
}
第8到41行是读取配置文件,可参照前面
分析Digester的文章,不再赘述。从第49行开始,即向localhost:8005发起一个Socket连接,并写入SHUTDOWN字符串。
这样将会关闭Tomcat中的那唯一的一个用户线程,接着所有守护线程将会退出(由JVM保证),之后整个应用关闭。
以上分析Tomcat的默认关闭机制,但这是通过运行脚本来关闭,我觉得这样比较麻烦,那么能不能通过一种在线访问的方式关闭Tomcat呢?当然可以,比较暴力的玩法是直接改org.apache.catalina.core.StandardServer的源码第500行,将
boolean match = command.toString().equals(shutdown);
改成
boolean match = command.toString().equals(“GET /SHUTDOWN HTTP/1.1”);
或者修改server.xml文件,找到Server节点,将原来的
<Server port="8005" shutdown="SHUTDOWN">
改成
<Server port="8005" shutdown="GET /SHUTDOWN HTTP/1.1">
这样直接在浏览器中输入http://localhost:8005/SHUTDOWN就可以关闭Tomcat了,原理?看懂了上面的文章,这个应该不难。
分享到:
相关推荐
标题中的“Tomcat启动和关闭”是指在Apache Tomcat服务器中进行服务的启停操作,这是每个Java Web开发者必备的基础技能。Apache Tomcat是一个开源的Servlet容器,它实现了Java Servlet和JavaServer Pages(JSP)规范...
Tomcat 组成及工作原理借鉴 Tomcat 是一个开源的 Web 服务器软件,由 Apache 软件基金会开发和维护。它支持 Servlet 和 JSP 规范,提供了一个强大和灵活的 Web 应用程序容器。 一、 Tomcat 背景 自从 JSP 发布...
1. `Server`:位于`org.apache.catalina.core`包下,是整个Tomcat服务器的顶级容器,包含了所有其他组件。 2. `Service`:每个`Server`可以包含多个`Service`,`Service`包含了Connector和Engine。 3. `Connector`:...
7. **资源释放**:最后,Tomcat会关闭HTTP连接,释放任何占用的资源,如线程、内存等。 在模拟Tomcat的过程中,了解并实现这些步骤可以帮助我们更好地理解Web服务器的工作流程,特别是Java的多线程模型在其中的应用...
【标题】:“Tomcat comet 服务器推技术” Tomcat 的 Comet 技术是一种基于 HTTP 长连接的服务器推送技术,允许服务器在客户端保持一个开放的 HTTP 连接,从而能够在数据准备好时立即推送到客户端,而无需客户端...
【Tomcat服务器】是Apache软件基金会的Jakarta项目下的一个开源免费Web应用服务器,它主要负责处理基于Java Servlet和JavaServer Pages(JSP)的应用。Tomcat作为一个轻量级的服务器,广泛应用于小型到中型企业级...
Tomcat工作原理深入解析 Tomcat作为一款广泛应用的开源Java Servlet容器,它的内部架构和工作流程对于理解Web应用的运行至关重要。本文将深入探讨Tomcat的主要组成部分,包括Server、Service、Connector、Engine、...
在Java世界中,开发一个类似Tomcat的服务器是一项复杂但有趣的任务。Tomcat是一个流行的开源应用服务器,主要用于运行Java Servlet和Java...通过这个过程,开发者可以深入理解Web服务器的工作原理,并提升自己的技能。
《深入理解Tomcat7 64位...总结,Tomcat7 64位版本的安装和配置并不复杂,但了解并掌握其内部工作原理和配置细节,对于提升Web应用的稳定性和性能至关重要。希望这篇指南能帮助你顺利地使用和管理64位的Tomcat7服务器。
- **SourceCodeDistributions源码**:提供Tomcat的完整源代码,开发者可以查看和修改源代码,了解其工作原理,甚至自定义构建。 总的来说,这个"tomcat7资源包"对于开发者、系统管理员或者学习Java Web技术的人来说...
- 同样,使用`shutdown.sh`或`shutdown.bat`来关闭Tomcat服务。 3. **部署Web应用** - 将WAR文件(Web应用的打包格式)直接放入`webapps`目录,Tomcat会自动解压并部署。 - 或者将应用的文件结构复制到`webapps`...
迷你版的Tomcat是一个小型化的Java Web服务器,它实现了...在实际的开发过程中,大型的Tomcat服务器会包含更多的功能,如多线程处理请求、连接池管理、会话管理、安全管理等,但这个迷你版为我们提供了一个很好的起点。
在Tomcat服务器中,我们可以通过修改`bin/catalina.sh`(或`catalina.bat`,取决于你的操作系统)脚本来添加这些参数。找到`JAVA_OPTS`变量,然后添加远程调试的选项,如下: ```bash JAVA_OPTS="-Djava.compiler=...
1. **客户端**发送HTTP请求到Tomcat服务器。 2. **Connector**接收请求,并将其转换成内部请求对象。 3. **Connector**将请求对象传递给对应的`Engine`。 4. `Engine`根据请求中的虚拟主机名选择合适的`Host`。 5. `...
**Tomcat7简介** Tomcat7是Apache软件基金会下的一个开源项目,它是Java Servlet、JavaServer Pages(JSP)和Java EE的Web应用服务器。...无论哪种方式,理解其工作原理和配置方法对于有效地使用Tomcat7至关重要。
后续请求会复用同一Servlet实例,直到服务器关闭或达到配置的实例最大数。 5. **JSP编译**:如果请求的是JSP页面,Tomcat会将其转换为对应的Servlet源代码(`.java`文件),然后编译成字节码(`.class`文件),再...
而在开发Java Web应用时,通常会与Tomcat服务器配合使用。了解Eclipse如何与Tomcat交互,特别是Eclipse如何调用Tomcat服务以及如何在Tomcat中部署项目是非常重要的。 #### Eclipse调用Tomcat服务的过程 1. **创建...