本文是Tomcat源码阅读系列的第三篇文章,本系列前两篇文章如下:
Tomcat源码阅读系列(一)使用IntelliJ IDEA运行Tomcat6源码
Tomcat源码阅读系列(二)Tomcat总体架构
本文主要介绍Tomcat的启动和关闭的过程。
org.apache.catalina.Lifecycle接口。Tomcat组件的声明周期的管理也就是由这个接口定义的。其重要定义如下:
org.apache.catalina.startup.Bootstrap类的main方法,从而启动Tomcat。
org.apache.catalina.startup.Bootstrap类的main方法启动Tomcat,而Bootstrap主要是通过反射的方式调用
org.apache.catalina.startup.Catalina对应的方法,如在Bootstrap中的start()方法的实现如下:
/** * Start the Catalina daemon. */ public void start() throws Exception { if( catalinaDaemon==null ) init();//catalinaDaemon为在init()方法中创建的org.apache.catalina.startup.Catalina的实例 Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null); method.invoke(catalinaDaemon, (Object [])null);//反射调用Catalina的start方法 }
/** * Execute a periodic task, such as reloading, etc. This method will be * invoked inside the classloading context of this container. Unexpected * throwables will be caught and logged. */ 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();//删除过期session } 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); }关于删除Session的操作,可以看一下具体实现,有助于对Session的理解。关于Session,本博会新开博文进行介绍。
"http-8080-1" daemon prio=6 tid=0x00000000080df800 nid=0x1968 in Object.wait() [0x00000000099ef000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000007d5f2f6b0> (a org.apache.tomcat.util.net.JIoEndpoint$Worker) at java.lang.Object.wait(Object.java:485) at org.apache.tomcat.util.net.JIoEndpoint$Worker.await(JIoEndpoint.java:458) - locked <0x00000007d5f2f6b0> (a org.apache.tomcat.util.net.JIoEndpoint$Worker) at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:484) at java.lang.Thread.run(Thread.java:662) Locked ownable synchronizers: - None "http-8080-Acceptor-0" daemon prio=6 tid=0x0000000007e19000 nid=0x2f4 runnable [0x00000000091af000] java.lang.Thread.State: RUNNABLE at java.net.PlainSocketImpl.socketAccept(Native Method) at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:408) - locked <0x00000007d7bd7c38> (a java.net.SocksSocketImpl) at java.net.ServerSocket.implAccept(ServerSocket.java:462) at java.net.ServerSocket.accept(ServerSocket.java:430) at org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:61) at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:352) at java.lang.Thread.run(Thread.java:662) Locked ownable synchronizers: - None "ContainerBackgroundProcessor[StandardEngine[Catalina]]" daemon prio=6 tid=0x000 00000075a1000 nid=0x2e8 waiting on condition [0x00000000090af000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1634) at java.lang.Thread.run(Thread.java:662) Locked ownable synchronizers: - None "main" prio=6 tid=0x000000000038b800 nid=0x175c runnable [0x000000000259e000] java.lang.Thread.State: RUNNABLE at java.net.PlainSocketImpl.socketAccept(Native Method) at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:408) - locked <0x00000007d60cbba0> (a java.net.SocksSocketImpl) at java.net.ServerSocket.implAccept(ServerSocket.java:462) at java.net.ServerSocket.accept(ServerSocket.java:430) at org.apache.catalina.core.StandardServer.await(StandardServer.java:430) at org.apache.catalina.startup.Catalina.await(Catalina.java:676) at org.apache.catalina.startup.Catalina.start(Catalina.java:628) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414) Locked ownable synchronizers: - None
/** * 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. */ public void await() { // Negative values - don't wait on port - tomcat is embedded or we just don't like ports // anything anything try { awaitSocket = new ServerSocket(port, 1, InetAddress.getByName("localhost")); } catch (IOException e) { log.error("StandardServer.await: create[" + 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 = null; long acceptStartTime = System.currentTimeMillis(); try { socket = serverSocket.accept(); socket.setSoTimeout(10 * 1000); // Ten seconds stream = socket.getInputStream(); } catch (SocketTimeoutException ste) { // This should never happen but bug 56684 suggests that // it does. log.warn(sm.getString("standardServer.accept.timeout", Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste); continue; } 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) { 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 } } } }这里ServerSocket监听的端口,以及对比的字符串都是在conf/server.xml中配置的,缺省情况下,配置如下:,从这里可以看出监听端口为8005,关闭请求发送的字符串为SHUTDOWN。通过上面的代码,我们可以看出在配置的端口上通过ServerSocket来监听一个请求的到来,如果请求的字符串和配置的字符串相同的话即跳出循环,这样的话await方法就执行完成,然后运行stop方法,运行完了以后,main线程就退出了。
telnet 127.0.0.1 8005 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. SHUTDOWN Connection closed by foreign host.运行telnet命令,并发送SHUTDOWN字符串以后,我们发现Tomcat就会退出await方法,然后执行stop方法最终停止。
public void stopServer(String[] arguments) { if (arguments != null) { arguments(arguments); } //.................anything // Stop the existing server s = getServer(); try { if (s.getPort()>0) { String hostAddress = InetAddress.getByName("localhost").getHostAddress(); Socket socket = new Socket(hostAddress, getServer().getPort()); OutputStream stream = socket.getOutputStream(); String shutdown = s.getShutdown(); for (int i = 0; i < shutdown.length(); i++) stream.write(shutdown.charAt(i)); stream.flush(); stream.close(); socket.close(); } else { log.error(sm.getString("catalina.stopServer")); System.exit(1); } } catch (IOException e) { log.error("Catalina.stop: ", e); System.exit(1); } }以上代码,向standardServer.getPort返回的端口(其实这里面返回即是conf/server.xml中Server根节点配置的port和shutdown属性)发送了s.getShutdown()返回的字符串,而默认情况下这个字符串就是SHUTDOWN。
ps aux | grep java ,kill -9
Runtime.getRuntime().addShutdownHook(shutdownHook);这个方法的意思就是在jvm中增加一个关闭的钩子,当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。所以这些钩子可以在jvm关闭的时候进行内存清理、对象销毁等操作。测试代码如下:
/** * Author: yangzhilong * Date: 2015/8/2 * Time: 0:30 */ public class TestShutdownHook { /** * @param args */ public static void main(String[] args) { // 定义线程1 Thread thread1 = new Thread() { public void run() { System.out.println("thread1..."); } }; // 定义线程2 Thread thread2 = new Thread() { public void run() { System.out.println("thread2..."); } }; // 定义关闭线程 Thread shutdownThread = new Thread() { public void run() { System.out.println("shutdownThread..."); } }; // jvm关闭的时候先执行该线程钩子 Runtime.getRuntime().addShutdownHook(shutdownThread); thread1.start(); thread2.start(); System.out.println("main..."); } }输出如下:
main... thread2... thread1... shutdownThread...shutdownThread 线程都是最后执行的(因为这个线程是在jvm执行关闭前才会执行)。