JVM 頻繁 FULL GC 快速排查整理

在分享此案例前,先聊聊哪些場景會導致頻繁Full GC:

內存泄漏(代碼有問題,對象引用沒及時釋放,導致對象不能及時回收)
死循環
大對象
程序執行了System.gc()

尤其是大對象,80%以上的情況就是他。  那麼大對象從哪裏來的:
【1】數據庫(包括 Mysql和 Mongodb等 NOSql數據庫),結果集太大;
【2】第三方接口傳輸的大對象;
【3】消息隊列,消息太大;

根據多年一線互聯網經驗,絕大部分情況是數據庫大結果集導致。好,現在我們開始介紹這次線上故障:

在沒有任何發佈的情況下,POP(point of purchase的縮寫bai,意爲“賣點廣告”其主要商du業用途是刺激引導zhi消費和活躍dao賣場氣氛)服務(接入第三方商家的服務)突然開始瘋狂Full GC,觀察堆內存監控沒內存泄漏,回滾到前一版本,問題仍然存在,尷尬了!!!

按照常規做法,一般先用 jmap導出堆內存快照(jmap -dump:format=b,file=文件名 [pid]),然後用 mat等工具分析出什麼對象佔用了大量空間,再查看相關引用找到問題代碼。爲了進一步排查原因,我們在線上開啓了 -XX:+HeapDumpBeforeFullGC在其中一臺機子上開啓了 -XX:HeapDumpBeforeFullGC,總體JVM參數如下:

-Xmx2g 
-XX:+HeapDumpBeforeFullGC 
-XX:HeapDumpPath=. 
-Xloggc:gc.log 
-XX:+PrintGC 
-XX:+PrintGCDetails 
-XX:+PrintGCDateStamps 
-XX:+UseGCLogFileRotation 
-XX:NumberOfGCLogFiles=10 
-XX:GCLogFileSize=100m 
-XX:HeapDumpOnOutOfMemoryError 

注意:JVM 在執行 dump操作的時候是會發生 stop the word事件的,也就是說此時所有的用戶線程都會暫停運行。爲了在此期間也能對外正常提供服務,建議採用分佈式部署,並採用合適的負載均衡算法

dump下來的文件大約 1.8g,用 jvisualvm查看,發現用 char[]類型的數據佔用了41%內存,同時另外一個 JdbcSqlStat類型的數據佔用了35%的內存,也就是說整個堆中幾乎全是這兩類數據。如下圖:

查看char[]類型數據,發現幾乎全是sql語句:

接下來查看char[]的引用情況:並對代碼進行修改

這種方式定位問題週期會比較長,如果是關鍵服務,長時間不能定位解決問題,影響太大。

下面來看看我們的做法。先按照常規做法分析堆內存快照,與此同時另外的同學去查看數據庫服務器網絡IO監控,如果數據庫服務器網絡IO有明顯上升,並且時間點吻合,基本可以確定是數據庫大結果集導致了Full GC,趕緊找DBA快速定位大SQL(對DBA來說很簡單,分分鐘搞定,如果DBA不知道怎麼定位,那他要被開除了,哈哈),定位到 SQL後再定位代碼就非常簡單了。按照這種辦法,我們很快定位了問題。原來是一個接口必傳的參數沒傳進來,也沒加校驗,導致 SQL語句 where後面少了兩個條件,一次查幾萬條記錄出來,真坑啊!這種方法是不是要快很多,哈哈,5分鐘搞定。

當時的 DAO層是基於 Mybatis實現的,出問題的SQL語句如下:

<select id="selectOrders" resultType="com.***.Order" >
    select * from user where 1=1
    <if test=" orderID != null ">and order_id = #{orderID}</if >
    <if test="userID !=null">and user_id=#{userID}</if >
    <if test="startTime !=null">and create_time >= #{createTime}</if >
    <if test="endTime !=null">and create_time <= #{userID}</if >
</select>

上面SQL語句意思是根據 orderID查一個訂單,或者根據 userID查一個用戶所有的訂單,兩個參數至少要傳一個。但是兩個參數都沒傳,只傳了 startTime和 endTime。所以一次 Select就查出了幾萬條記錄。所以我們在使用 Mybatis的時候一定要慎用 if test,一不小心就會帶來災難。後來我們將上面的SQL拆成了兩個:

根據訂單ID查詢訂單:

<select id="selectOrderByID" resultType="com.***.Order" >
    select * from user where
    order_id = #{orderID}
    <if test="startTime !=null">and create_time >= #{createTime}</if >
    <if test="endTime !=null">and create_time <= #{userID}</if >
</select>

根據 userID查詢訂單:

<select id="selectOrdersByUserID" resultType="com.***.Order" >
    select * from user whereuser_id=#{userID}
    <if test="startTime !=null">and create_time >= #{createTime}</if >
    <if test="endTime !=null">and create_time <= #{userID}</if >
</select>


----關注公衆號,獲取更多內容----

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章