
https://www.cnblogs.com/tylorliu/p/6978049.html
- 環境:系統測試(Windows Server/JRE8/tomcat7)
- 現象:應用運作幾天後,出現通路逾時,伺服器cpu使用率居高不下
- 問題日志:OutOfMemoryError:MetaSpace
- 問題分析:
- metaSpace設定過小,不足應用所需
- 應用metaSpace持續增長,超過metaSpace限制
- 原因分析:MetaSpace是jvm存放類資訊的記憶體空間,發生溢出的可能原因:
- 定位:問題最先從DeviceStatusMonitorTask中報出,而這個定時任務新版本修改了同步裝置狀态的功能,主要是與vag通信擷取裝置狀态資訊。
- 猜測:
- 裝置狀态監控任務中動态生成代理類,導緻metaSpace不斷消耗
- 重制:
- 本地運作應用,添加多個可用裝置,縮短task執行間隔
- 開啟Java VisualVM監控
- 限制Metaspace最大值:-XX:MaxMetaspaceSize=100m
從JVisualVM的監控視圖中,我們可以直覺的看出每隔一分鐘都會出現線程數飙升、類加載數階梯式增長的情況。
随着類加載數的增長,Metaspace空間逐漸從60M增長到100M,出現記憶體溢出,導緻jvm頻繁觸發full GC,消耗大量CPU資源。
一、分析——>找出問題代碼
Task類 run方法代碼:
紅框部分為新增代碼,具體實作如下:
主要邏輯是與底層元件通信查詢運作狀态,然後根據結果更新狀态。直接排除DAO操作的嫌疑,抽取與通信部分,整理成單獨的測試代碼:
步驟:
- 設定jvm參數 : -verbose:class 列印類加載資訊
- 清理控制台日志,調試代碼。
調試過程中,我發現每次循環都會有新的類被加載:
而這些類都是在下面這行代碼運作之後加載的。
結合類加載資訊以及sendRequest方法的實作,基本确認問題是由JaxbUtil處理xml、JavaBean的互相轉換引起。
繼續調試分析,發現JAXBContext對象初始化時會動态加載class,而JaxbUtil每次調用都會重新建立一個JAXBContext。
二、解決方案
問題根因既已找到,解決思路自然清晰明确。
考慮到jdk中已有JAXB工具類提供xml和javaBean的互轉,借鑒源碼發現JAXB使用弱引用Cache對象來緩存JAXBContext。
/** * Cache. We don't want to prevent the {@link Cache#type} from GC-ed, * hence {@link WeakReference}. */ private static volatile WeakReference cache; /** * Obtains the {@link JAXBContext} from the given type, * by using the cache if possible. * * * We don't use locks to control access to {@link #cache}, but this code * should be thread-safe thanks to the immutable {@link Cache} and {@code volatile}. */ private static JAXBContext getContext(Class<T> type) throws JAXBException { WeakReference c = cache; if(c!=null) { Cache d = c.get(); if(d!=null && d.type==type) return d.context; } // overwrite the cache Cache d = new Cache(type); cache = new WeakReference(d); return d.context; }
結合應用的實際場景,上面的實作避免了短時間頻繁建立JAXBContext。但是弱引用Cache在無引用的情況下會很快被GC回收,是以每次定時任務都會重新生成context;并且Cache對象隻能存儲一個context,在定時任務的運作過程中可能由于其他接口通信導緻context切換。綜上,JAXB的實作也無法滿足目前應用的需要。
沒有現成的解決方案,隻好自己寫一個。
由建立JAXBContext引起問題,那就延長對象的生命周期,減少建立對象。對于相同的Class,可以使用同一個context對象與xml互相轉換。由于vag的接口個數有限, 其xml封包格式并不多,是以,維護一個static Map, JAXBContext>來存儲context對象占用的記憶體并不多。考慮到與vag通信屬于并發執行,使用ConcurrentHashMap實作保證并發安全。
最終代碼如下:
三、結果驗證
将之前的測試代碼模拟定時任務略微修改,每隔10s執行一次,重複50次。
開啟JVisualVM監視視圖,從圖中可以明确的看出類裝載數在第一次循環時就已接近最大值,後續過程中隻加載了極少數量的class,證明這種方案确實可行。
使用修改後的代碼運作整個項目,16小時後的監視圖像顯示:類加載數保持穩定,MetaSpace大小幾乎無變化。
四、總結
-
排查問題的思路适用于一般的jvm永久代或元空間溢出。
-
主要采用單例模式的思想解決建立大量複雜對象引起的資源消耗問題。
另外,前段時間還使用-verbose:class 參數排查出Apache CXF生成的webservice用戶端重複初始化引起的OOM問題的原因。用戶端初始化的過程中也會根據wsdl檔案動态生成class并加載,是以,使用CXF用戶端代碼時,應盡量使用單例模式。
林老師帶你學程式設計
微信号 : lzqcode
網址:wolzq.com
覺得好看,請點這裡↓↓↓