深入理解 Tomcat(七)源碼剖析 Tomcat 完整啟動過程

前言

這是我們分析 Tomcat 的第七篇文章,前面我們依據(jù)啟動過程理解了類加載過程帆调,生命周期組件奠骄,容器組件等》基本上將啟動過程拆的七零八落含鳞,分析的差不多了, 但是還沒有從整體的視圖下來分析Tomcat 的啟動過程芹务。因此蝉绷,這篇文章的任務(wù)就是這個,我們想將Tomcat的啟動過程徹底的摸清枣抱,把它最后一件衣服扒掉熔吗。然后我們就分析連接器和URL請求了,不再留戀這里了佳晶。

好吧桅狠。我們開始吧。

說到Tomcat的啟動轿秧,我們都知道中跌,我們每次需要運行tomcat/bin/startup.sh這個腳本,而這個腳本的內(nèi)容到底是什么呢菇篡?我們來看看漩符。

1. startup.sh 腳本內(nèi)容

#!/bin/sh
os400=false
case "`uname`" in
OS400*) os400=true;;
esac

# resolve links - $0 may be a softlink
PRG="$0"

while [ -h "$PRG" ] ; do
  ls=`ls -ld "$PRG"`
  link=`expr "$ls" : '.*-> \(.*\)$'`
  if expr "$link" : '/.*' > /dev/null; then
    PRG="$link"
  else
    PRG=`dirname "$PRG"`/"$link"
  fi
done

PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh

# Check that target executable exists
if $os400; then
  # -x will Only work on the os400 if the files are:
  # 1. owned by the user
  # 2. owned by the PRIMARY group of the user
  # this will not work if the user belongs in secondary groups
  eval
else
  if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
    echo "Cannot find $PRGDIR/$EXECUTABLE"
    echo "The file is absent or does not have execute permission"
    echo "This file is needed to run this program"
    exit 1
  fi
fi

exec "$PRGDIR"/"$EXECUTABLE" start "$@"

樓主刪除了一些無用的注釋,我們來看看這腳本逸贾。該腳本中有2個重要的變量:

  1. PRGDIR:表示當(dāng)前腳本所在的路徑
  2. EXECUTABLE:catalina.sh 腳本名稱
    其中最關(guān)鍵的一行代碼就是 exec "$PRGDIR"/"$EXECUTABLE" start "$@"陨仅,表示執(zhí)行了腳本catalina.sh津滞,參數(shù)是start铝侵。

2. catalina.sh 腳本實現(xiàn)

然后我們看看catalina.sh 腳本中的實現(xiàn):

elif [ "$1" = "start" ] ; then

  if [ ! -z "$CATALINA_PID" ]; then
    if [ -f "$CATALINA_PID" ]; then
      if [ -s "$CATALINA_PID" ]; then
        echo "Existing PID file found during start."
        if [ -r "$CATALINA_PID" ]; then
          PID=`cat "$CATALINA_PID"`
          ps -p $PID >/dev/null 2>&1
          if [ $? -eq 0 ] ; then
            echo "Tomcat appears to still be running with PID $PID. Start aborted."
            echo "If the following process is not a Tomcat process, remove the PID file and try again:"
            ps -f -p $PID
            exit 1
          else
            echo "Removing/clearing stale PID file."
            rm -f "$CATALINA_PID" >/dev/null 2>&1
            if [ $? != 0 ]; then
              if [ -w "$CATALINA_PID" ]; then
                cat /dev/null > "$CATALINA_PID"
              else
                echo "Unable to remove or clear stale PID file. Start aborted."
                exit 1
              fi
            fi
          fi
        else
          echo "Unable to read PID file. Start aborted."
          exit 1
        fi
      else
        rm -f "$CATALINA_PID" >/dev/null 2>&1
        if [ $? != 0 ]; then
          if [ ! -w "$CATALINA_PID" ]; then
            echo "Unable to remove or write to empty PID file. Start aborted."
            exit 1
          fi
        fi
      fi
    fi
  fi

  shift
  touch "$CATALINA_OUT"
  if [ "$1" = "-security" ] ; then
    if [ $have_tty -eq 1 ]; then
      echo "Using Security Manager"
    fi
    shift
    eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
      -classpath "\"$CLASSPATH\"" \
      -Djava.security.manager \
      -Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \
      -Dcatalina.base="\"$CATALINA_BASE\"" \
      -Dcatalina.home="\"$CATALINA_HOME\"" \
      -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
      org.apache.catalina.startup.Bootstrap "$@" start \
      >> "$CATALINA_OUT" 2>&1 "&"

  else
    eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
      -classpath "\"$CLASSPATH\"" \
      -Dcatalina.base="\"$CATALINA_BASE\"" \
      -Dcatalina.home="\"$CATALINA_HOME\"" \
      -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
      org.apache.catalina.startup.Bootstrap "$@" start \
      >> "$CATALINA_OUT" 2>&1 "&"

  fi

  if [ ! -z "$CATALINA_PID" ]; then
    echo $! > "$CATALINA_PID"
  fi

  echo "Tomcat started."

該腳本很長,但我們只關(guān)心我們感興趣的:如果參數(shù)是 start触徐, 那么執(zhí)行這里的邏輯咪鲜,關(guān)鍵再最后一行執(zhí)行了 org.apache.catalina.startup.Bootstrap "$@" start, 也就是說撞鹉,執(zhí)行了我們熟悉的main方法疟丙,并且攜帶了start 參數(shù)颖侄,那么我們就來看Bootstrap 的main方法是如何實現(xiàn)的。

3. Bootstrap.main 方法實現(xiàn)

    public static void main(String args[]) {

        System.err.println("Have fun and Enjoy! cxs");

        // daemon 就是 bootstrap
        if (daemon == null) {
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }

        try {
            String command = "start";
            if (args.length > 0) {
                command = args[args.length - 1];
            }
            if (command.equals("startd")) {
                args[args.length - 1] = "start";
                daemon.load(args);
                daemon.start();
            }
            else if (command.equals("stopd")) {
                args[args.length - 1] = "stop";
                daemon.stop();
            }
            else if (command.equals("start")) {
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();
            } else if (command.equals("stop")) {
                daemon.stopServer(args);
            } else if (command.equals("configtest")) {
                daemon.load(args);
                if (null==daemon.getServer()) {
                    System.exit(1);
                }
                System.exit(0);
            } else {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable t) {
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }
    }

我們看看該方法享郊, 首先 bootstrap.init() 的方法用于初始化類加載器览祖,我們已經(jīng)分析過該方法了,就不再贅述了炊琉,然后我們看下面的try塊展蒂,默認(rèn)命令行參數(shù)是 start ,但我們剛剛的腳本傳的參數(shù)就是 start苔咪, 因此進入該if塊

   else if (command.equals("start")) {
         daemon.setAwait(true);
         daemon.load(args);
         daemon.start();
  1. 設(shè)置catalina 的 await 屬性為true锰悼;
  2. 運行 catalina 的 load 方法。該方法內(nèi)部主要邏輯是解析server.xml文件团赏,初始化容器箕般。我們已經(jīng)再生命周期那篇文章中講過容器的初始化。
  3. 運行 catalina 的 start 方法舔清。也就是啟動 tomcat丝里。這個部分我們上次分析了容器啟動。但是容器之后的邏輯我們沒有分析体谒。今天我們就來看看丙者。

4. Catalina.start 方法

 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");
        }
        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();
        }
    }

該方法我們上次分析到了 getServer().start() 這里,也就是容器啟動的邏輯营密,我們不再贅述械媒。
今天我們繼續(xù)分析下面的邏輯。主要邏輯是:

 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();
        }

可以看到是 Runtime.getRuntime().addShutdownHook(shutdownHook)方法评汰。那么這個方法的作用是什么呢纷捞?JDK 文檔是這樣說的:

注冊新的虛擬機來關(guān)閉鉤子。
只是一個已初始化但尚未啟動的線程被去。虛擬機開始啟用其關(guān)閉序列時主儡,它會以某種未指定的順序啟動所有已注冊的關(guān)閉鉤子,并讓它們同時運行惨缆。運行完所有的鉤子后糜值,如果已啟用退出終結(jié),那么虛擬機接著會運行所有未調(diào)用的終結(jié)方法坯墨。最后寂汇,虛擬機會暫停。注意捣染,關(guān)閉序列期間會繼續(xù)運行守護線程骄瓣,如果通過調(diào)用方法來發(fā)起關(guān)閉序列,那么也會繼續(xù)運行非守護線程耍攘。

簡單來說榕栏,如果用戶的程序出現(xiàn)了bug畔勤, 或者使用control + C 關(guān)閉了命令行,那么就需要做一些內(nèi)存清理的工作扒磁。該方法就會再虛擬機退出時做清理工作庆揪。再ApplicationShutdownHooks 類種維護著一個IdentityHashMap<Thread, Thread> Map,用于后臺清理工作妨托。那么該線程對象的run方法中是什么邏輯呢嚷硫?我們來看看:

5. CatalinaShutdownHook.run 線程方法實現(xiàn)

 protected class CatalinaShutdownHook extends Thread {
        @Override
        public void run() {
            try {
                if (getServer() != null) {
                    Catalina.this.stop();
                }
            } catch (Throwable ex) {
                ExceptionUtils.handleThrowable(ex);
                log.error(sm.getString("catalina.shutdownHookFail"), ex);
            } finally {
                // If JULI is used, shut JULI down *after* the server shuts down
                // so log messages aren't lost
                LogManager logManager = LogManager.getLogManager();
                if (logManager instanceof ClassLoaderLogManager) {
                    ((ClassLoaderLogManager) logManager).shutdown();
                }
            }
        }
    }

該線程是Catalina的內(nèi)部類,方法邏輯是始鱼,如果Server容器還存在仔掸,就是執(zhí)行Catalina的stop方法用于停止容器。(為什么要用Catalina.this.stop 呢医清?因為它繼承了Thread起暮,而Thread也有一個stop方法,因此需要顯式的指定該方法)最后關(guān)閉日志管理器会烙。我們看看stop方法的實現(xiàn):

6. Catalina.stop 方法實現(xiàn):

public void stop() {

        try {
            // Remove the ShutdownHook first so that server.stop()
            // doesn't get invoked twice
            if (useShutdownHook) {
                Runtime.getRuntime().removeShutdownHook(shutdownHook);

                // If JULI is being used, re-enable JULI's shutdown to ensure
                // log messages are not lost
                LogManager logManager = LogManager.getLogManager();
                if (logManager instanceof ClassLoaderLogManager) {
                    ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                            true);
                }
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            // This will fail on JDK 1.2. Ignoring, as Tomcat can run
            // fine without the shutdown hook.
        }

        // Shut down the server
        try {
            Server s = getServer();
            LifecycleState state = s.getState();
            if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
                    && LifecycleState.DESTROYED.compareTo(state) >= 0) {
                // Nothing to do. stop() was already called
            } else {
                s.stop();
                s.destroy();
            }
        } catch (LifecycleException e) {
            log.error("Catalina.stop", e);
        }
    }

該方法首先移除關(guān)閉鉤子负懦,為什么要移除呢,因為他的任務(wù)已經(jīng)完成了柏腻。然后設(shè)置useShutdownHook 為true纸厉。最后執(zhí)行Server的stop方法,Server的stop方法基本和init方法和start方法一樣五嫂,都是使用父類的模板方法颗品,首先出發(fā)事件,然后調(diào)用stopInternal沃缘,該方法內(nèi)部循環(huán)停止子容器躯枢,子容器遞歸停止,和我們之前的邏輯一致槐臀,不再贅述锄蹂。destroy方法同理。

好了水慨,我們已經(jīng)看清了關(guān)閉鉤子的邏輯得糜,其實就是開辟一個守護線程交給虛擬機,然后虛擬機在某些異常情況(比如System.exit(0))前執(zhí)行停止容器的邏輯晰洒。

好朝抖。我們回到start方法。

7. 回到 Catalina.start 方法

在設(shè)置好關(guān)閉鉤子后欢顷,tomcat 的啟動過程還沒有啟動完畢槽棍,接下來的邏輯式什么呢?

        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();
        }

在設(shè)置完關(guān)閉鉤子之后抬驴,會將 useShutdownHook 這個變量為false炼七,然后執(zhí)行 await 方法。然后執(zhí)行stop方法布持,我們記得stop方法式關(guān)閉容器的方法豌拙,神經(jīng)病啊,好不容易啟動了题暖,為什么又要關(guān)閉呢按傅? 先不著急,我們還是看看 await 方法吧,該方法調(diào)用了Server.await 方法胧卤,我們來看看:

8. Catalian.await 方法實現(xiàn)

注意:該方法很長

    @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
                }
            }
        }
    }

我們看一下他的邏輯:首先創(chuàng)建一個socketServer 鏈接唯绍,然后循環(huán)等待消息。如果發(fā)過來的消息為字符串SHUTDOWN, 那么就break枝誊,停止循環(huán)况芒,關(guān)閉socket。否則永不停歇叶撒【В回到我們剛剛的疑問,await 方法后面執(zhí)行 stop 方法祠够,現(xiàn)在一看就合情合理了压汪,只要不發(fā)出關(guān)閉命令,則不會執(zhí)行stop方法古瓤,否則則繼續(xù)執(zhí)行關(guān)閉方法止剖。

到現(xiàn)在,Tomcat 的整體啟動過程我們已經(jīng)了然于胸了落君,總結(jié)一下就是:

  1. 初始化類加載器滴须。
  2. 初始化容器并注冊到JMX后啟動容器。
  3. 設(shè)置關(guān)閉鉤子叽奥。
  4. 循環(huán)等待關(guān)閉命令扔水。

等一下。好像缺了點什么朝氓?魔市?? Tomcat 啟動后就只接受關(guān)閉命令赵哲,接受的http請求怎么處理待德,還要不要做一個合格的服務(wù)器了?枫夺?将宪? 別急,實際上,這個是主線程较坛,負(fù)責(zé)生命周期等事情印蔗。處理Http請求的線程在初始化容器和啟動容器的時候由子容器做了,這塊的邏輯我們下次再講丑勤。大家不要疑惑华嘹。

9. 我們知道了Tomcat 是怎么啟動的,那么是怎么關(guān)閉的呢法竞?

順便說說關(guān)閉的邏輯:

shutdown.sh 腳本同樣會調(diào)用 Bootstrap的main 方法耙厚,不同是傳遞 stop參數(shù), 我們看看如果傳遞stop參數(shù)會怎么樣:

ry {
            String command = "start";
            if (args.length > 0) {
                command = args[args.length - 1];
            }
            if (command.equals("startd")) {
                args[args.length - 1] = "start";
                daemon.load(args);
                daemon.start();
            }
            else if (command.equals("stopd")) {
                args[args.length - 1] = "stop";
                daemon.stop();
            }
            else if (command.equals("start")) {
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();
            } else if (command.equals("stop")) {
                daemon.stopServer(args);
            } else if (command.equals("configtest")) {
                daemon.load(args);
                if (null==daemon.getServer()) {
                    System.exit(1);
                }
                System.exit(0);
            } else {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable t) {

可以看到調(diào)用的是 stopServer 方法岔霸,實際上就是 Catalina的stopServer 方法薛躬,我們看看該方法實現(xiàn):

10. 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);
        }
    }

注意,該停止命令的虛擬機和啟動的虛擬機不是一個虛擬機呆细,因此型宝,沒有初始化 Server , 進入 IF 塊,解析 server.xml 文件侦鹏,獲取文件中端口诡曙,用以創(chuàng)建Socket。然后像啟動服務(wù)器發(fā)送 SHUTDOWN 命令略水,關(guān)閉啟動服務(wù)器价卤,啟動服務(wù)器退出剛剛的循環(huán),執(zhí)行后面的 stop 方法渊涝,最后退出虛擬機慎璧,就是這么簡單。

11. 總結(jié)

我們從整體上解析了Tomcat的啟動和關(guān)閉過程跨释,發(fā)現(xiàn)不是很難胸私,為什么?因為我們之前已經(jīng)分析過很多遍了鳖谈,有些邏輯我們已經(jīng)清除了岁疼,這次分析只是來掃尾。復(fù)雜的Tomcat的啟動過程我們基本就分析完了缆娃。我們知道了啟動和關(guān)閉都依賴Socket捷绒。只是我們驚奇的發(fā)現(xiàn)他的關(guān)閉竟然是如此實現(xiàn)。很牛逼贯要。我原以為會像我們平時一樣暖侨,直接kill。哈哈哈崇渗。

好吧字逗。今天我們就到這里 京郑,tomcat 這座大山我們已經(jīng)啃的差不多了,還剩一個 URL 請求過程和連接器葫掉,這兩個部分是高度關(guān)聯(lián)的些举,因此,樓主也會將他們放在一起分析挖息。透過源碼看真相金拒。

連接器兽肤,等著我們來撕開你的衣服L赘埂!W收 电禀!

good luck !s孕荨<夥伞!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末店雅,一起剝皮案震驚了整個濱河市政基,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌闹啦,老刑警劉巖沮明,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異窍奋,居然都是意外死亡荐健,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門琳袄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來江场,“玉大人,你說我怎么就攤上這事窖逗≈贩瘢” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵碎紊,是天一觀的道長佑附。 經(jīng)常有香客問我,道長矮慕,這世上最難降的妖魔是什么帮匾? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮痴鳄,結(jié)果婚禮上瘟斜,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好螺句,可當(dāng)我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布虽惭。 她就那樣靜靜地躺著,像睡著了一般蛇尚。 火紅的嫁衣襯著肌膚如雪芽唇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天取劫,我揣著相機與錄音匆笤,去河邊找鬼。 笑死谱邪,一個胖子當(dāng)著我的面吹牛炮捧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惦银,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼咆课,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扯俱?” 一聲冷哼從身側(cè)響起书蚪,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎迅栅,沒想到半個月后殊校,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡库继,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年箩艺,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宪萄。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡艺谆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拜英,到底是詐尸還是另有隱情静汤,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布居凶,位于F島的核電站虫给,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏侠碧。R本人自食惡果不足惜抹估,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望弄兜。 院中可真熱鬧药蜻,春花似錦瓷式、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至踱卵,卻和暖如春廊驼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背惋砂。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工妒挎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人班利。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓饥漫,卻偏偏與公主長得像榨呆,于是被迫代替她去往敵國和親罗标。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容