記一次JVM Metaspace溢出排查

多圖預警!java

  • 環境:系統測試(Windows Server/JRE8/tomcat7)
  • 現象:應用運行幾天後,出現訪問超時,服務器cpu利用率居高不下
  • 問題日誌:OutOfMemoryError:MetaSpace
  • 問題分析:
    • 緣由分析:MetaSpace是jvm存放類信息的內存空間,發生溢出的可能緣由:
      • metaSpace設置太小,不足應用所需
      • 應用metaSpace持續增加,超過metaSpace限制
    • 定位:問題最早從DeviceStatusMonitorTask中報出,而這個定時任務新版本修改了同步設備狀態的功能,主要是與vag通訊獲取設備狀態信息。
  • 猜想:
    • 設備狀態監控任務中動態生成代理類,致使metaSpace不斷消耗
  • 重現:
    • 本地運行應用,添加多個可用設備,縮短task執行間隔
    • 開啓Java VisualVM監控
    • 限制Metaspace最大值:-XX:MaxMetaspaceSize=100m

  

  從JVisualVM的監控視圖中,咱們能夠直觀的看出每隔一分鐘都會出現線程數飆升、類加載數階梯式增加的狀況。web

  隨着類加載數的增加,Metaspace空間逐步從60M增加到100M,出現內存溢出,致使jvm頻繁觸發full GC,消耗大量CPU資源。緩存

  •  分析——>找出問題代碼

  Task類 run方法代碼:tomcat

  紅框部分爲新增代碼,具體實現以下:安全

      主要邏輯是與底層組件通訊查詢運行狀態,而後根據結果更新狀態。直接排除DAO操做的嫌疑,抽取與通訊部分,整理成單獨的測試代碼:服務器

步驟: 1. 設置jvm參數 : -verbose:class  打印類加載信息併發

2. 清理控制檯日誌,調試代碼。jvm

調試過程當中,我發現每次循環都會有新的類被加載:工具

而這些類都是在下面這行代碼運行以後加載的。測試

結合類加載信息以及sendRequest方法的實現,基本確認問題是由JaxbUtil處理xml、JavaBean的相互轉換引發。

繼續調試分析,發現JAXBContext對象初始化時會動態加載class,而JaxbUtil每次調用都會從新建立一個JAXBContext。

  •  解決方案

問題根因既已找到,解決思路天然清晰明確。

    考慮到jdk中已有JAXB工具類提供xml和javaBean的互轉,借鑑源碼發現JAXB使用弱引用Cache對象來緩存JAXBContext。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

/**

    * Cache. We don't want to prevent the {@link Cache#type} from GC-ed,

    * hence {@link WeakReference}.

    */

   private static volatile WeakReference<Cache> cache;

 

   /**

    * Obtains the {@link JAXBContext} from the given type,

    * by using the cache if possible.

    *

    * <p>

    * 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 <T> JAXBContext getContext(Class<T> type) throws JAXBException {

       WeakReference<Cache> 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<Cache>(d);

 

       return d.context;

   }

結合應用的實際場景,上面的實現避免了短期頻繁建立JAXBContext。可是弱引用Cache在無引用的狀況下會很快被GC回收,因此每次定時任務都會從新生成context;而且Cache對象只能存儲一個context,在定時任務的運行過程當中可能因爲其餘接口通訊致使context切換。綜上,JAXB的實現也沒法知足當前應用的須要。

    沒有現成的解決方案,只好本身寫一個。

由建立JAXBContext引發問題,那就延長對象的生命週期,減小新建對象。對於相同的Class,可使用同一個context對象與xml互相轉換。因爲vag的接口個數有限, 其xml報文格式並很少,所以,維護一個static Map<Class<?>, JAXBContext>來存儲context對象佔用的內存並很少。考慮到與vag通訊屬於併發執行,使用ConcurrentHashMap實現保證併發安全。

最終代碼以下:

  •  結果驗證

將以前的測試代碼模擬定時任務略微修改,每隔10s執行一次,重複50次。

開啓JVisualVM監視視圖,從圖中能夠明確的看出類裝載數在第一次循環時就已接近最大值,後續過程當中只加載了極少數量的class,證實這種方案確實可行。

使用修改後的代碼運行整個項目,16小時後的監視圖像顯示:類加載數保持穩定,MetaSpace大小几乎無變化。

  • 總結

    • 排查問題的思路適用於通常的jvm永久代或元空間溢出。
    • 主要採用單例模式的思想解決建立大量複雜對象引發的資源消耗問題。

另外,前段時間還使用-verbose:class 參數排查出Apache CXF生成的webservice客戶端重複初始化引發的OOM問題的緣由。客戶端初始化的過程當中也會根據wsdl文件動態生成class並加載,所以,使用CXF客戶端代碼時,應儘可能使用單例模式。

相關文章
相關標籤/搜索