最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Tomcat出現(xiàn)假死原因及解決方法

 更新時間:2023年11月05日 10:36:05   作者:學(xué)習(xí)小學(xué)生  
線上環(huán)境因為有個接口內(nèi)部在錯誤的參數(shù)下,會不斷生成字符串,導(dǎo)致OOM,在OOM之后服務(wù)還能正常運行,但是發(fā)送的Api請求已經(jīng)沒有辦法響應(yīng)了,本文小編給大家詳細(xì)介紹了Tomcat出現(xiàn)假死原因及解決方法,需要的朋友可以參考下

1. 問題背景

線上環(huán)境因為有個接口內(nèi)部在錯誤的參數(shù)下,會不斷生成字符串,導(dǎo)致OOM,在OOM之后服務(wù)還能正常運行,但是發(fā)送的Api請求已經(jīng)沒有辦法響應(yīng)了。

2. 問題復(fù)現(xiàn)

模擬線上問題,在測試環(huán)境上進行復(fù)現(xiàn),一段時間后服務(wù)會爆出OOM,但是不是每次都會導(dǎo)致Tomcat假死,有些情況下Tomcat還能正常訪問。

情況一:核心線程丟失

OOM之前Tomcat線程情況

ID線程名稱Group
125http-nio-9989-Acceptor-0main
126http-nio-9989-AsyncTimeoutmain
123http-nio-9989-ClientPoller-0main
124http-nio-9989-ClientPoller-1main
113http-nio-9989-exec-1main

OOM之后Tomcat線程情況

ID線程名稱Group
123http-nio-9989-ClientPoller-0main
1431http-nio-9989-exec-103main

情況二:服務(wù)重啟

日志打印java.lang.OutOfMemoryError: Java heap space后,服務(wù)重啟。

情況三:Tomcat后臺線程丟失

只有后臺線程丟失,但是Acceptor線程和Poller線程還存在

3. 假死情況

從Tomcat的NIO模型得知有幾個組件,Acceptor、Poller、業(yè)務(wù)線程池。這三個組件情況如下:

  • Acceptor線程:該線程主要是監(jiān)聽連接(socket.accept()),如果該線程掛掉,那么及時操作系統(tǒng)層面TCP3次握手成功,但是業(yè)務(wù)上也辦法獲取到這個連接。默認(rèn)情況下,只有1個Acceptor線程,可以通過acceptorThreadCount參數(shù)設(shè)置。
  • Poller線程:Acceptor獲取到連接之后,會輪詢從Poller列表中取一個Poller進行處理。如果Poller線程掛掉了,那么就沒法處理讀請求了。默認(rèn)情況下,會有min(2,cpu核數(shù))個Poller線程,可以通過pollerThreadCount參數(shù)設(shè)置。
  • 業(yè)務(wù)線程:Poller線程將讀請求放到業(yè)務(wù)線程處理,如果業(yè)務(wù)線程阻塞(比如被某個網(wǎng)絡(luò)IO阻塞),那么此刻的讀請求還在業(yè)務(wù)線程池的隊列中,沒有被處理。默認(rèn)情況下,最小線程為10,可以通過minSpareThreads參數(shù)設(shè)置,最大線程為200,可以通過maxThreads參數(shù)設(shè)置。

此時分析再結(jié)合復(fù)現(xiàn)的情況,如果核心線程掛掉,那確實存在假死情況。但是從事發(fā)現(xiàn)場來看,并沒有發(fā)現(xiàn)Tomcat的Acceptor、Poller線程打印出OutofMemoryError的異常,及時將org.apache.tomcat和org.apache.catalina設(shè)置成Debug級別。因此需要深入源碼分析。

4. 異常處理分析

4.1 Acceptor異常處理分析

Acceptor邏輯如下,就是在循環(huán)內(nèi)不斷地監(jiān)聽accept(),查看是否有新連接。

//NioEndpoint$Acceptor#run
protected class Acceptor extends AbstractEndpoint.Acceptor {
    @Override
    public void run() {
        int errorDelay = 0;
        // Loop until we receive a shutdown command
        while (running) {
            //...忽略一些代碼
            state = AcceptorState.RUNNING;
            try {
                //if we have reached max connections, wait
                countUpOrAwaitConnection();
                SocketChannel socket = null;
                try {
                    socket = serverSock.accept();
                } catch (IOException ioe) {
                }
                // Successful accept, reset the error delay
                errorDelay = 0;
                // Configure the socket
                if (running && !paused) {
                    // setSocketOptions() will hand the socket off to
                    // an appropriate processor if successful
                    if (!setSocketOptions(socket)) {
                        closeSocket(socket);
                    }
                } else {
                    closeSocket(socket);
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString("endpoint.accept.fail"), t);
            }
        }
        state = AcceptorState.ENDED;
    }
	//...忽略一些代碼
}

其中setSocketOptions是往Poller中調(diào)用register方法,把這個Socket傳遞過去。getPoller0()方法會以輪詢的策略獲取一個Poller

//NioEndpoint#setSocketOptions
protected boolean setSocketOptions(SocketChannel socket) {
    // Process the connection
    try {
        //disable blocking, APR style, we are gonna be polling it
        socket.configureBlocking(false);
        Socket sock = socket.socket();
        socketProperties.setProperties(sock);

        NioChannel channel = nioChannels.pop();
        if (channel == null) {
            SocketBufferHandler bufhandler = new SocketBufferHandler(
                    socketProperties.getAppReadBufSize(),
                    socketProperties.getAppWriteBufSize(),
                    socketProperties.getDirectBuffer());
            if (isSSLEnabled()) {
                channel = new SecureNioChannel(socket, bufhandler, selectorPool, this);
            } else {
                channel = new NioChannel(socket, bufhandler);
            }
        } else {
            channel.setIOChannel(socket);
            channel.reset();
        }
        getPoller0().register(channel);
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        try {
            log.error("",t);
        } catch (Throwable tt) {
            ExceptionUtils.handleThrowable(tt);
        }
        // Tell to close the socket
        return false;
    }
    return true;
}

此處重點看一下ExceptionUtils.handleThrowable方法的邏輯,因為OutofMemoryError是VirtualMachineError的子類,所以這里會被直接拋出異常,。而OutofMemeoryError屬于 uncheck exception,拋出uncheck exception就會導(dǎo)致線程終止,并且主線程和其他線程無法感知這個線程拋出的異常。如果線程代碼(run方法之外)之外來捕獲這個異常的話,可以通過Thread的setUncaughtExceptionHandler處理。

//ExceptionUtils#handleThrowable
public static void handleThrowable(Throwable t) {
    if (t instanceof ThreadDeath) {
        throw (ThreadDeath) t;
    }
    if (t instanceof StackOverflowError) {
        // Swallow silently - it should be recoverable
        return;
    }
    if (t instanceof VirtualMachineError) {
        throw (VirtualMachineError) t;
    }
    // All other instances of Throwable will be silently swallowed
}

再看啟動的時候,線程是否會設(shè)置uncaughtExceptionHandler,發(fā)現(xiàn)并沒有設(shè)置,所以異常沒法被正常打印到日志中。

//AbstractEndpoint#startAcceptorThreads
protected final void startAcceptorThreads() {
    int count = getAcceptorThreadCount();
    acceptors = new Acceptor[count];

    for (int i = 0; i < count; i++) {
        acceptors[i] = createAcceptor();
        String threadName = getName() + "-Acceptor-" + i;
        acceptors[i].setThreadName(threadName);
        Thread t = new Thread(acceptors[i], threadName);
        t.setPriority(getAcceptorThreadPriority());
        t.setDaemon(getDaemon());
        t.start();
    }
}

小結(jié):OutofMemoryError被捕獲了,然后重新拋出,但是因為OutofMemoryError是uncheck exception,而線程沒有設(shè)置uncaughtExceptionHandler,所以沒法被打印。

4.2 增加全局異常捕獲

在啟動的時候,設(shè)置全局線程nncaughtException處理器。這里簡單打印線程名稱,并且拋出異常。

Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        logger.error("[Global Handler]thread-name:{},happen exp,", t.getName(), e);
    }
});

重新復(fù)現(xiàn)問題,發(fā)現(xiàn)Acceptor線程有打印異常情況。

不過,同時也發(fā)現(xiàn),Poller線程有打印錯誤日志,但并不是全局處理器打印的。

下圖為Arthas截圖,發(fā)現(xiàn)仍然Poller線程仍然存在。因此再分析Poller的異常處理。

4.3 Poller異常處理

從上面的異常日志倆看,Poller線程是在處理PollerEvent中處理REGISTER事件時的拋出異常,查看相關(guān)代碼。發(fā)現(xiàn)此處捕獲的是Exception,而OutofMemoryError屬于Error,所以此處不會被捕獲到,并且會往上拋出。

//NioEndpoint$Poller#events
public void run() {
    if (interestOps == OP_REGISTER) {
        try {
            socket.getIOChannel().register(
                    socket.getPoller().getSelector(), SelectionKey.OP_READ, socketWrapper);
        } catch (Exception x) {
            log.error(sm.getString("endpoint.nio.registerFail"), x);
        }
    }
    //...
}

在Poller的events方法中,會循環(huán)調(diào)用PollerEvent的run方法,這里內(nèi)部有捕獲一個Throwable,而Error是繼承Throwable。所以O(shè)utofMemoryError會在這里被捕獲,而且會打印日志,并且線程不會掛掉。

//NioEndpoint$Poller#events
public boolean events() {
    boolean result = false;

    PollerEvent pe = null;
    for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++ ) {
        result = true;
        try {
            pe.run();
            pe.reset();
            if (running && !paused) {
                eventCache.push(pe);
            }
        } catch ( Throwable x ) {
            log.error("",x);
        }
    }

    return result;
}

而在Poller的循環(huán)中,發(fā)現(xiàn)也有ExceptionUtils.handleThrowable處理,如果在這里出現(xiàn)OutofMemoryError異常的話,那么Poller線程將會被終止。

//NioEndpoint$Poller#run
public class Poller implements Runnable {
	public void run() {
		// Loop until destroy() is called
		while (true) {
			Boolean hasEvents = false;
			try {
				if (!close) {
					hasEvents = events();
					//....
				}
			}catch (Throwable x) {
				ExceptionUtils.handleThrowable(x);
				log.error("",x);
				continue;
			}
            //...
		}
	}
}

小結(jié):Poller內(nèi)部實現(xiàn)中,對于異常處理不同,有些地方能捕獲異常并且Poller線程正常處理,有些地方?jīng)]有捕獲異常,可能會因為OutofMemoryError導(dǎo)致線程終止

5. 結(jié)論

當(dāng)應(yīng)用程序出現(xiàn)OOM的時候,Tomcat核心線程有可能會掛掉,導(dǎo)致接口接口無法正常訪問,因此要盡量避免業(yè)務(wù)上出現(xiàn)OOM。此外,當(dāng)出現(xiàn)OOM后應(yīng)用無法訪問時,可以試著排查一下,是不是tomcat的核心線程掛掉導(dǎo)致。

以上就是Tomcat出現(xiàn)假死原因及解決方法的詳細(xì)內(nèi)容,更多關(guān)于Tomcat假死的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 解決Tomcat?Caused?by:?java.lang.ClassNotFoundException:?java.util.logging.Logger的問題

    解決Tomcat?Caused?by:?java.lang.ClassNotFoundException:?ja

    這篇文章主要給大家介紹了如何解決Tomcat?Caused?by:?java.lang.ClassNotFoundException:?java.util.logging.Logger的問題,文中有詳細(xì)的原因分析及解決方法,需要的朋友可以參考下
    2023-10-10
  • Tomcat 啟動閃退常見問題18種解決方法及示例

    Tomcat 啟動閃退常見問題18種解決方法及示例

    Tomcat啟動閃退可能由多種原因引起,包括配置錯誤、資源限制、兼容性問題等,本文主要介紹了Tomcat 啟動閃退常見問題18種解決方法及示例,具有一定的參考價值,感興趣的可以了解一下
    2024-06-06
  • 詳解tomcat 解決 POST請求傳參數(shù)過長受限制的問題

    詳解tomcat 解決 POST請求傳參數(shù)過長受限制的問題

    這篇文章主要介紹了詳解tomcat 解決 POST請求傳參數(shù)過長受限制的問題,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • Tomcat正常訪問localhost報404問題解決

    Tomcat正常訪問localhost報404問題解決

    這篇文章主要介紹了Tomcat正常訪問localhost報404問題解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • Tomcat目錄結(jié)構(gòu)詳解

    Tomcat目錄結(jié)構(gòu)詳解

    Tomcat 服務(wù)器是一個免費的開放源代碼的 Web 應(yīng)用服務(wù)器,學(xué)習(xí)tomcat目錄結(jié)構(gòu)十分有必要,今天小編給大家?guī)砹嗽敿?xì)教程,感興趣的朋友一起看看吧
    2019-11-11
  • Tomcat的webapps文件夾使用

    Tomcat的webapps文件夾使用

    這篇文章主要介紹了Tomcat的webapps文件夾使用,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • 如何修改Tomcat默認(rèn)訪問根目錄

    如何修改Tomcat默認(rèn)訪問根目錄

    文章描述了如何在Tomcat中配置和訪問webapps/guanwang目錄下的文件,在Linux和Windows系統(tǒng)中,配置方法相同,只是文件夾地址有所差別,在該目錄下上傳了一個文本文件,可以直接訪問其內(nèi)容
    2024-12-12
  • redission-tomcat快速實現(xiàn)從單機部署到多機部署詳解

    redission-tomcat快速實現(xiàn)從單機部署到多機部署詳解

    這篇文章主要介紹了redission-tomcat快速實現(xiàn)從單機部署到多機部署詳解,本文介紹一個基于redis的tomcat session管理開源項目:redission-tomcat,可無代碼侵入式地快速實現(xiàn)session共享,需要的朋友可以參考下
    2019-06-06
  • 使用IDEA配置tomcat及創(chuàng)建JSP文件的方法

    使用IDEA配置tomcat及創(chuàng)建JSP文件的方法

    這篇文章主要介紹了使用IDEA配置tomcat及創(chuàng)建JSP文件的方法,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-05-05
  • 一個tomcat下部署多個項目的詳細(xì)步驟

    一個tomcat下部署多個項目的詳細(xì)步驟

    一個tomcat下如何部署多個項目,Linux跟windows系統(tǒng)下的步驟都差不多,以下linux系統(tǒng)下部署為例,windows系統(tǒng)下部署同理,接下來小編就給大家詳細(xì)的介紹一下在一個tomcat下部署多個項目的操作步驟,需要的朋友可以參考下
    2023-08-08

最新評論

海兴县| 苗栗市| 常德市| 长春市| 昂仁县| 沿河| 洛隆县| 聊城市| 乳山市| 酒泉市| 珠海市| 石嘴山市| 耒阳市| 合水县| 沐川县| 尼木县| 福州市| 东乡族自治县| 贵港市| 临海市| 鄯善县| 蓬溪县| 六枝特区| 蕲春县| 定襄县| 永修县| 锦州市| 合江县| 新龙县| 宜宾县| 谷城县| 靖江市| 通辽市| 瓦房店市| 婺源县| 开化县| 南充市| 康马县| 门源| 新安县| 江川县|