起因
線上新運行的一個微服務,啓動過程到穩定之後,服務一致沒有問題,但是在運行一小段時間之後,經常服務狀態不可用,訪問改服務的Restful接口處於503狀態
初步分析
剛開始的時候並沒有意識到這個問題的嚴重性,以爲是服務器突發的問題 ,採取的手段 就是暴力重啓,可是重啓之後一小段時間內確實沒問題,但是之後又服務不可用了
通常 情況下,HTTP 503的狀態碼標示當前的服務響應有問題,難道是總線的通信 有問題?觀察 其他在該節點上的服務狀態之後發現,他們都響應的很及時,這個服務是怎麼了,撲街了?
這個時候的直覺告訴我,這個服務肯定是運行一段時間之後崩掉了,但是log裏邊爲什麼沒有報OOM的 錯誤呢?查看了下服務的啓動參數,發現沒有顯示的打印內存溢出日誌,二話不說在啓動參數裏邊配置下-XX:+HeapDumpOnOutOfMemoryError,別說,還真發現了異常日誌:Java OutOfMemoryError: Metaspace
Metaspace異常
拿到了異常堆棧用MAT分析工具統計了一下,發現報告中分析內存 可能泄露的地方都是在調用URLClassLoader和Sysytem的ClassLoader,不太像是程序引起的內存泄露呀
【異常情況下的內存快照】
【服務啓動什麼都不做情況下的內存快照】
通過Jmap dump 服務正常啓動之後的內存快照發現,異常場景下的快照文件中的內存並沒有增加多少呀,爲什麼會引起內存溢出呢?
雖然還是不清楚問題產生的原因,爲了分析此問題,決定把啓動參數的metaspace空間調大一些,再來看看正常執行結束之後dump的內存快照到底是什麼樣的
【XX:MaxMetaspaceSize擴大一倍之後的快照】
拿到Metaspace空間擴容之後的內存快照文件之後瞬間變得不淡定了,納尼,怎麼分析的結論還是ClassLoader的問題?而且前後感覺只增加了10M呀,啓動參數XX:MaxMetaspaceSize設置的是64M,遠還沒有到達這個峯值呀?瞬間不淡定了。
冷靜下來之後分析了下這個現象,可以肯定的是,元空間(MetaspaceSize)的數據肯定發生過增加,肯定有一部分的class或者常量老化到這裏,但是爲什麼這個空間增長的不多,卻還是內存溢出了呢?莫非是發生過GC,回收掉了?但是存在矛盾呀,MetaspaceSize的數據都基本是class一些數據,還有靜態常量文件,不可能會有顯著的GC效果呀?
Jconsole監控內存變化
爲了搞定這個問題,決定要監控一下JVM的內存使用情況,這讓我想到了用Jconsle來看一下整個運行過程中的元空間內存變化過程,服務啓動添加如下配置參數,Jconsole連接對應的IP和端口號進行連接
JAVA_OPTS="$JAVA_OPTS -Djava.rmi.server.hostname=10.10.10.111.1 -Dcom.sun.management.jmxremote"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.port=12345"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.local.only=false"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.ssl=false"
這一連上,不得了,我的個乖乖,服務啓動正常之後的內存就跑到快60M了,而且執行執行代碼代碼邏輯之後,元空間數據又
老化了15M左右
從內存監控來看 ,看來我們內存溢出確實是因爲原來分配的最大元數據空間內存64M是不夠用的,因爲啓動穩定之後都接近60M了,程序在執行過程中再老化一些數據,肯定會導致內存溢出的,這也就說明了開頭的那個問題。
如果規避這個問題,我們之前調大這個參數就解決了這個問題,但是現實生產環境對硬件資源的要求肯定是有限度的呀,爲什麼啓動就需要這麼大的內存?執行任務過程中爲什麼會老化一些數據?對於這個問題還是要刨根問底、。
首先,在服務運行過程中,我們看到上圖對應的Metaspace空間增長是線性的,並沒有短時間內急劇增加內存,說明這個可能是正常現象,而且我們通過MAT分析了啓動穩定之後和執行程序之後的對象佔用,並沒有發現有某一種類型的對象發生過陡峭增長。
【啓動穩定後Class分佈】
*【執行程序之後的Class分佈】
優化手段
在查閱資料過程中發現,JVM 有這麼一個參數,-XX:MaxMetasaceFreeRatio=N ,當進行過Metaspace GC之後, 會計算當前Metaspace的空閒空間比,如果空閒比大於這個參數,那麼虛擬機會釋放Metaspace的部分空間,我設置的參數爲50,爲了驗證 他的GC情況,在啓動參數中添加了-XX:+PrintGCDetails -Xloggc:…/logs/gc.log -XX:+PrintGCTimeStamps 日誌,但是GC日誌並沒有正常數據,MaxMetasace的空間也沒有看到明顯的減少
由於這部分空間主要放的是Class的信息,標準的甲骨文/ Sun VM旁觀世界是:類是永遠的。所以一旦加載,即使沒有人在意,他們也會留在記憶中。我們瞭解到JVM 還有個配置參數-XX:+CMSClassUnloadingEnabled,他可以掃描MaxMetasace區,卸載掉不在使用的類,配置這個參數之後並沒有發現較大的提升,但是可以防止重複創建class而導致的內存泄露
降低啓動時MaxMetasace內存
遺留問題
這裏這個問題 雖然解決了,但是還有一個遺留問題,爲什麼 明明Metaspace佔用的空間都超過60M了,dump的 快照只有30多M?