首頁>技術>

我們在工作中的Java應用伺服器中遇到了非常奇怪的記憶體洩漏:在部署新版本的微服務時,JVM程序記憶體不足,因此崩潰,導致服務中斷。

經過一番研究,這類錯誤似乎在這個應用伺服器中非常常見,尤其是在部署應用程式時不重新啟動伺服器時。常見的修復方法是在投入生產之前重新啟動JVM程序,防止記憶體不足(但不會導致記憶體洩漏)。這就是我們選擇的短期“修復”。

得到記憶體洩露的證據記錄堆記憶體

我們懷疑某個特定的庫是記憶體洩漏源,首先要做的是確保它確實是問題所在。為此,我啟動了應用伺服器的本地例項,並在其上部署了WAR。然後,我使用jmap建立了堆記憶體的快照dump檔案。

jmap -dump:live,file=first.bin <pid>

一旦我有了備份,我已經開始重新部署我的WAR檔案7次,沒有重新啟動伺服器。

最後,我用jmap建立了另一個堆快照。

記憶體分析

一旦有了這兩個堆快照,我就使用Eclipse記憶體分析器MAT來讀取dump檔案。以下是我發現的:

Size: 85.3 MB Classes: 23.5k Objects: 1.9m Class Loader: 436

消耗了853 MB。我個人認為這是一個可以接受的應用程式。讓我們進入第二個GC:

Size: 271.9 MB Classes: 35k Objects: 7.1m Class Loader: 1.4k

我們可以看出有一個明顯的問題。7次部署後,記憶體消耗增加了兩倍。某處有明顯的記憶體洩漏。是時候採取行動了。

瞭解記憶體洩漏問題所在

既然我確信記憶體洩漏了,我已經使用jmap來檢視記憶體細節,瞭解是什麼消耗了這麼多記憶體。結果令人驚訝:

371 instances of "*ClassLoader", loaded by "jdk.internal.loader.ClassLoaders$AppClassLoader @ 0x7e021a658" occupy 198,789,800 (??.??%) bytes.Biggest instances:* ClassLoader @ 0x7ef531c30 - 27,782,296 (9.74%) bytes.* ClassLoader @ 0x7ee056470 - 27,781,552 (9.74%) bytes.* ClassLoader @ 0x7e6658b18 - 27,781,208 (9.74%) bytes.* ClassLoader @ 0x7ec60ab60 - 27,780,856 (9.74%) bytes.* ClassLoader @ 0x7ef531cd8 - 27,780,032 (9.74%) bytes.* ClassLoader @ 0x7ea3074b8 - 27,779,608 (9.74%) bytes.* ClassLoader @ 0x7e31b53b0 - 27,200,584 (9.54%) byte

如您所見,記憶體中有很多類裝入器。最大的例項是以前部署的例項。它們還沒有被GC清理乾淨,這就解釋了記憶體洩漏的原因: 有些東西使這些例項以及它們包含的所有資料保持了活動狀態。

在Java中GC是如何工作的

在搜尋記憶體洩漏的原因之前,瞭解Java垃圾回收的工作原理非常重要。使用的演算法稱為標記和掃描。簡而言之,它是如何工作的:

在Java中,有一些特殊的物件不能在應用程式執行時被垃圾回收。這些物件稱為GC根。例如,actives執行緒、主類中的靜態變數、系統類裝入器、系統類等…

因此,演算法是這樣進行的:它將從GC根開始構建一種樹,並嘗試透過引用它們的用法來確定每個活動物件的路徑。當演算法完成時,所有未連線到GC根的物件都將成為垃圾回收的候選物件。下面的模式對此進行了解釋:

因此,如果我們的類載入器在部署後仍然處於活動狀態,這意味著我們的應用程式中的某些東西正在將它“連結”到GC根,從而阻止任何垃圾收集。現在我知道該找什麼了。

追蹤記憶體洩漏問題

Eclipse記憶體分析器有一個非常有用的函式,名為“path to GC roots”,它顯示了是什麼使特定的類保持活動狀態。以下是我發現的:

* ClassLoader @ 0x7ee056470* * contextClassLoader io.github.classgraph.ScanResult$1* * * [...]* * * * hooks java.lang.ApplicationShutdownHooks @ 0x7e00863b8 (System class)

如你所見,可疑庫在內部使用類相簿在類裝入器上執行一些操作。這個 ClassGraph 庫在 ApplicationShutdownHooks 類(這是一個系統類,因此是一個GC根)上註冊了一個 shutdownhook 。 ApplicationShutdownHooks 用於註冊在JVM關閉時要執行的特殊程式碼,由於我們的JVM在我們的情況下沒有重新啟動(請記住,我們是在不重新啟動的情況下進行部署的),所以鉤子永遠不會被呼叫,因此仍然是活動的,保持對 ScanResult 物件的引用,防止它成為GC,從而防止我們的整個類載入器也成為它。 我們找到兇手了!

希望ClassGraph是開源的,所以我查找了報告的問題,發現了一些有趣的東西。

已經有人報告了這個bug,它在4.8.51版本中得到了解決。但是這個bug仍然存在,我已經查看了可疑的庫原始碼,你猜怎麼著?他們使用的是4.6.32。bug還在那裡。

原文連結:http://javakk.com/1132.html

28
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • ArcGIS 同時上下標的標註實現