當考慮Java中的內存泄漏時,我們通常會考慮Java堆泄漏,即在堆中分配的對象沒有被垃圾收集。這是我在處理一臺服務器內存泄漏時的想法,但我即將經歷的遠超出我的想象。
癥狀:運行Vertx應用程序(沒有交換分區)的生產服務器被Linux內存不足kill掉(操作系統機制,當系統出現內存緊張的情況時釋放內存)崩潰。
因為它是生產服務器,所以我認為可以讓我們使用堆轉儲和MAT來檢查發生了什么,并嘗試找出誰在消耗這么多內存。
結果令人驚訝,java堆是合理的,遠遠少于進程內存的足跡。有什么東西侵蝕了我的內存,我不知道那是什么。
我的出發點是在home目錄中創建的java致命錯誤日志,所以我開始研究這些日志。您可以在下面的頁面中關于java致命錯誤日志的信息:致命錯誤日志致命錯誤日志可以提供很多有價值的信息并節省時間,因此我建議您仔細閱讀。致命錯誤日志顯示堆大小小于2G,但進程正在增長到大約3-4G字節,這是怎么回事?
Java進程包含以下內存空間:
堆-分配對象的位置。
線程堆棧-包含所有線程堆棧。
Metaspace-包含元數據類(替換Java7和更早版本中的PermGen)。
代碼緩存-JIT編譯器代碼緩存。
堆緩沖池不足。
操作系統內存-本機操作系統內存。
我用了命令:
jmap -heap [pid]
它打印了JVM堆大小的摘要,還顯示了堆大小約為1.5GByte。
我檢查了元空間的大小,但只有幾兆字節。也許代碼緩存是問題所在?我再次檢查了致命錯誤日志,我看到只有大約20M。我得出的結論是,可能我有本機內存泄漏即-XX:NativeMemoryTracking=detail,然后使用jcmd實用程序(包含在JDK中)檢查本機內存。在下一頁中閱讀有關NMT的更多信息:NMT結果包含以下內容:
Internal (reserved=1031767KB, committed=1031767KB)
(malloc=1031735KB #7619)
(mmap: reserved=32KB, committed=32KB)
我發現JVM有大約1Gbyte大小的內存。現在我確信我有
我發現有巨大的malloc(操作系統內存分配調用)內存分配,但我仍然不知道是什么原因造成的。我試著用gdb處理內存轉儲,但效果不好,我得出的結論是它可能不會有用,所以我搜索了Linux內存泄漏檢測工具。我有幾個候選人:
Valgrind
malloc_tracer
前兩個由于某些原因不起作用,所以嘗試了人們推薦的jemalloc。我在應用程序啟動腳本中添加了以下行:
MALLOC_CONF=prof_leak:true,prof_final:true,lg_prof_interval:30,lg_prof_sample:17 \
LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 [jre path]/jre/bin/java [app parameters]
一開始jemalloc不起作用,我不得不用configure參數-enable prof重新編譯它,然后它就開始工作了。關閉應用程序后,jemalloc在工作目錄中創建了reproof文件。
[jemalloc install dir]/bin/jeprof --show_bytes --pdf ‘[jre path]/jre/bin/java' [jeprof file] > [pdf output file name]
jemalloc分析表明,使用malloc os調用分配內存的“Unsafe_AllocateMemory”存在嚴重泄漏。我原以為我現在就能得到答案,但顯然我錯了。我在谷歌上搜索了一下,發現不安全的分配內存可能與名為sun.misc.Unsafe是執行本機內存分配的JDK私有類,正如它的名稱所表明的那樣,它是不安全的(Oracle計劃在java9中刪除這個類,但最終它仍保留在不受支持的模塊中)。我搜索了應用程序代碼,但沒有找到它的任何用法,我假設它可能是應用程序的一些庫使用的。主要嫌疑人是Netty,Netty是Vertx使用的網絡庫。Netty提供了很好的性能,但它使用本機內存分配來實現這一點。在Netty源代碼中挖掘發現它正在使用`sun.misc.Unsafe`分配本機內存池。Netty包含內存泄漏檢測機制,因此我嘗試使用Netty參數:
-Dio.netty.leakDetection.level=advanced But that dind't supply any output.
我試著通過使用其他netty參數(沒有很好的文檔)來限制netty:
-Dio.netty.noPreferDirect=true
-Dio.netty.allocator.type=unpooled
-Dio.netty.maxDirectMemory=0
但這也不管用。
經過多次嘗試,我最終發現直接內存分配可以通過以下jvm參數來限制:
-XX:MaxDirectMemorySize=[max memory]
在深入研究jvm之后,我終于找到了解決方案。我學到了很多關于jvm機制和內存空間的知識。