一次由於八股文引起的記憶體洩漏

張哥說技術發表於2024-02-08

來源:阿里雲開發者

阿里妹導讀

本文記錄兩次報錯系統監控現象以及作者針對性的排查過程和分析,最終解決了問題的全過程。
文章開頭,先分享一張大部分Java開發同學都記在心裡的一張圖。

一次由於八股文引起的記憶體洩漏

沒錯,就是Spring Bean生命週期圖。就因為這張圖不熟悉,導致線上環境出現記憶體洩漏問題,系統頻繁FullGC,服務無法響應。

一、第一次報錯系統監控現象

一次由於八股文引起的記憶體洩漏

一次由於八股文引起的記憶體洩漏

關鍵時間節點:

14:16 機器釋出新程式碼
15:35 機器開始出現fullGC
15:50 機器fullGC耗時上升
17:48 對JVM進行dump操作,然後進行機器置換

由圖可知,在14:16釋出完成後,系統正常執行了一段時間,期間記憶體、執行緒等均未出現異常,不過當系統執行了一段時間後,透過監控可以明顯發現記憶體使用量和執行緒數都在持續上升,那這樣問題就很明確了:

1.有大量阻塞執行緒

2.存在記憶體洩露問題


1.1 排查過程

分析執行緒Dump檔案

一次由於八股文引起的記憶體洩漏

Dump檔案記錄
透過截圖中Dump檔案內容可知,HSFBizProcessor-DEFAULT-9-thread-792 這個執行緒已經阻塞了116s,並且的阻塞執行緒共有682個。


1.2 分析原因

根據執行緒堆疊資訊,查到了執行緒是阻塞在下面這段程式碼:


















@Componentpublic class OssClient implements BeanPostProcessor {private OSS ossClient = null;/**      * 初始化OSS客戶端     **/@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {// 省略程式碼……// 以下是阻塞程式碼行        ossClient = new OSSClientBuilder().build(ossProperty.getString("endpoint"),                        ossProperty.getString("accessKeyId"),                        ossProperty.getString("accessKeySecret"),                        configuration);// 省略程式碼……return bean;    }}
這段程式碼本意是在應用啟動時,透過動態配置檔案來配置OSS客戶端。
但是執行緒阻塞在了這行,首先我想到可能是由於OSS客戶端初始化需要發起網路請求,因為餓了麼有張北和南通機房且一般情況下跨機房無法訪問,所以第一時間檢查了一下配置,果不其然,南通機房配置了張北的OSS。
登入上南通機房的機器,嘗試PING張北的OSS域名,發現無法PING通,驗證了我的猜測。

一次由於八股文引起的記憶體洩漏


1.3 第一次問題解決

Get到了報錯原因,就方便解決了;透過修改配置,將OSS機房配置正確後,重啟機器即可。

二、第二次報錯系統監控現象

本來以為萬事大吉,在觀察了30分鐘,確認系統無BLOCKED執行緒後,就認為該問題已經解決。

一次由於八股文引起的記憶體洩漏

一次由於八股文引起的記憶體洩漏

關鍵時間節點:

19:48 機器釋出新程式碼
22:30 機器開始出現fullGC
23:30 機器fullGC耗時上升
00:30 對JVM進行dump操作,然後進行機器置換

然而,在釋出後3個小時以後,系統又開始報錯,同樣是fullGC,只不過這次fullGC耗時沒有之前那麼長了。


2.1 排查過程

分析執行緒Dump檔案

因為有了前車之鑑,所以第一步想到的就是上一步的問題沒有解決,執行緒仍然阻塞在剛才的程式碼處。

一次由於八股文引起的記憶體洩漏


不過,這次並沒有查詢到阻塞執行緒。這至少證明:

1.阻塞執行緒確實是由於OSS跨單元拒絕訪問導致的

2.還有其他問題導致了記憶體洩漏

分析GC Dump檔案

首先,透過集團Grace工具,發現有嚴重的記憶體洩漏問題。

一次由於八股文引起的記憶體洩漏

這裡顯示有11萬個org.apache.http.impl.conn.PoolingHttpClientConnectionManager例項,佔用了80.42%的堆記憶體,但是這個類並不是我直接引入的,那麼一定是有間接依賴,生成了大量該類物件。

另外,透過類名,能判斷這個物件是和網路請求有關係,而我這個應用上需要網路請求的地方有幾處:
1.訪問DB
2.訪問Redis
3.訪問OSS
4.進行HSF呼叫

繼續透過對物件依賴進行分析,發現了一個重要資訊:

一次由於八股文引起的記憶體洩漏

org.apache.http.impl.conn.PoolingHttpClientConnectionManager這個類由OSS間接依賴進來的,確定了引起記憶體洩漏的罪魁禍首。


2.2 分析原因

雖然定位到了是由於OSS建議依賴進來,但是看程式碼仍然不能解釋為什麼會產生記憶體洩漏。


















@Componentpublic class OssClient implements BeanPostProcessor {private OSS ossClient = null;/**      * 初始化OSS客戶端     **/@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {// 省略程式碼……// 一下是阻塞程式碼行        ossClient = new OSSClientBuilder().build(ossProperty.getString("endpoint"),                        ossProperty.getString("accessKeyId"),                        ossProperty.getString("accessKeySecret"),                        configuration);// 省略程式碼……return bean;    }}

排查原因過程中,有一篇文章給了我答案,下面是這篇文章給的OOM原因的解釋:

每次new OSSClient的時候,都會往List中放入HttpClientConnectionManager,但是沒有主動呼叫OSSClient的shutdown的方法,所以List只會增大不會變小。反觀我們的程式碼,每次介面呼叫都會建立一個OSSClient物件,但卻在使用完之後,沒有呼叫OSSClient的shutdown方法,導致未呼叫IdleConnectionReaper的removeConnectionManager方法,使得IdleConnectionReaper中靜態列表儲存的PoolingHttpClientConnectionManager例項資料一直會增長,一直都不會被回收,最終帶來的結果就是OOM。

其實透過程式碼能夠看出,我的初衷是在OssClient這個Bean初始化的時候執行一下初始化邏輯,在我查到導致記憶體洩漏的原因後,我仍然對一個問題很是不解:為什麼OSS初始化的程式碼會被多次執行?
回到文章標題和開頭,為什麼這篇文章標題叫“一次由於八股文引起的記憶體洩漏”,以及為什麼文章開頭會引入下面這張圖?

一次由於八股文引起的記憶體洩漏

實際上,是由於實現錯了介面導致的OSS初始化程式碼被重複呼叫,最終導致系統OOM。


2.3 最終問題解決

改變一下實現介面,使程式碼邏輯符合我預期效果即可,當然這個解決方式有多種多樣,下面只是我的一種解決方案。




















@Componentpublic class OssClient implements InitializingBean {
   private OSS ossClient = null;
   /**     * 初始化OSS客戶端     **/    @Override    public void afterPropertiesSet() throws Exception {        // 省略程式碼……        // 以下是阻塞程式碼行        ossClient = new OSSClientBuilder().build(ossProperty.getString("endpoint"),                        ossProperty.getString("accessKeyId"),                        ossProperty.getString("accessKeySecret"),                        configuration);        // 省略程式碼……    }
}

總結

圈內常有聲音抱怨,“面試好比是造火箭,而工作不過是擰螺絲”,尤其對於Java開發崗位面試中的常規知識題目持有輕蔑態度。然而,這些被稱作“八股文”的知識,實際上是每位開發工程師技術根基的核心。堅實的基礎才能確保構建在其之上的高樓大廈能夠屹立不倒,歷經歲月的洗禮。

來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70024923/viewspace-3006592/,如需轉載,請註明出處,否則將追究法律責任。

相關文章