1. 程式人生 > >JNI引起的堆外記憶體洩漏問題分析

JNI引起的堆外記憶體洩漏問題分析

背景

客戶現場的監控系統中有一個網路聽診器功能,其每隔1分鐘會對全網裝置進行ping操作,以此來儘可能快的發現裝置及網路是否出現異常。暫且不說通過該功能來對裝置及網路作健康檢測是否靠譜。由於JAVA對於網路層以下的協議是無能為力的,而ping操作涉及ICMP與ARP協議,因此監控系統只能藉助JNI機制來搞定。

BUG現象

監控系統的java.exe程序每隔幾個小時就異常退出

問題定位


  1. 通過應用系統的日誌看是否為業務相關的異常引起的 –》日誌中並無任何異常資訊
  2. 開啟GC日誌,並觀察一段時間,看是否存在堆記憶體回收異常(洩漏或溢位) –》堆記憶體一切正常
  3. 此時忽然想起,java.exe程序異常退出應該會生成相關的hs_err.log檔案,果然在應用目錄下找到了一堆錯誤檔案。該日誌也叫crash日誌。
  4. 通過檢視hs_err.log內容得知,原來是jni ping引入的dll呼叫異常導致java.exe程序異常中止了。
PS:如果能早點想起步驟3,那就不用浪費步驟2的功夫了。

JNI呼叫異常分析

JNI異常導致java程序中止的原因可能為

  • JVM自己的BUG:谷歌了一把,網上描述的BUG中,現場的JDK版本都已經修復了。
  • JNI DLL的BUG:這個原因範圍就大了,至此只能根據經驗猜測可能的原因,然後一個一個排除了。

由於linux環境下有這麼一個機制:當核心檢測到程序的實體記憶體不斷增加至某一個值時,核心會直接將該程序kill掉。

windows是否也有這樣的機制呢?目前尚未查證,還請高手解答。

在沒有進一步證據的前提下,只能先猜測是否為程序實體記憶體出了問題,於是監測了下應用程序的實體記憶體損耗量,果然是緩慢遞增的,但JVM堆記憶體仍然一切正常,由此大約知道是堆外記憶體使用上出了問題。

關於堆外記憶體的相關知識,可參考下面的文章:

至此,可以知道該問題與JAVA沒啥關係了,但為了徹底搞明白,我還是硬著頭皮找來DLL的C原始碼,想看看是否可以用我helloworld級別的C水平把這個問題搞定。

堆外記憶體[洩漏、異常]分析

分析C/C++應用的記憶體,大夥一般都會想到perftool,可惜windows環境下我始終編譯不過。於是谷歌上再搜尋一把”windows記憶體洩漏”,發現知乎上有

文章推薦了一堆,但我要麼下載不到,要麼看不懂。最後是根據《C/C++記憶體洩漏及檢測》介紹的方法定位到是dll中有一段程式碼使用了快取導致記憶體洩漏,當記憶體達到JVM中設定的MaxDirectMemorySize值時,dll就會出現記憶體訪問異常錯誤,最終導致java.exe程序異常退出了。

PS:在定位堆外記憶體異常相關問題時,為了快速重現問題,可以將MaxDirectMemorySize改小,MaxDirectMemorySize的預設值可認為與-Xmx設定的值一樣(嚴格上不是,參見JVM原始碼分析之堆外記憶體完全解讀

總結

該問題並非通用性問題,寫這篇文章主要是為了記錄下當時解決該問題的整個定位過程,文中一些知識點可能表述有誤,還請批評指正。