Docker 的 Java 內(nèi)存消耗異常怎么辦?

本文來自于HeapDump性能社區(qū)! !有性能問題,上HeapDump性能社區(qū)!
最近,我所在的團(tuán)隊(duì)在部署我們的微服務(wù)(AWS 上的 Docker 中的 Java+SpringMVC)時(shí)遇到了問題,一個(gè)非常輕量級(jí)的應(yīng)用卻消耗了太多內(nèi)存。于是,我們?cè)?Docker 中發(fā)現(xiàn)了許多關(guān)于 Java 內(nèi)存的線索,并找到了通過重構(gòu)和遷移到 Spring Boot 來減少其消耗的解決方法。

這里分享一下整個(gè)過程:

在部署微服務(wù)之前,我們慣例要預(yù)估下內(nèi)存,于是制定了一個(gè)清晰且簡(jiǎn)單的方程式來找到RSS

RSS = Heap size + MetaSpace + OffHeap size

這里的OffHeap由線程堆棧、緩沖區(qū)、庫 (*.jars) 和 JVM 代碼本身組成。

Resident Set Size是當(dāng)前分配給進(jìn)程并由進(jìn)程使用的 RAM 量。它包括代碼、數(shù)據(jù)和共享庫。

我們根據(jù)本地 Java VisualVM 值來查找:

1.png

2.png

3.png
RSS = 253(Heap) + 100(Metaspace) + 170(OffHeap) + 52*1(Threads) = 600Mb (max avarage)

……看完這個(gè),當(dāng)時(shí)覺得萬事大吉,就等完成了擼串去!

既然大概600Mb就夠了。我們就選擇了一個(gè) t2.micro AWS 實(shí)例(具有 1Gb RAM)進(jìn)行了部署。

首先,去JVM 設(shè)置了一些和內(nèi)存相關(guān)的配置:

-XX:MinHeapFreeRatio=10 \
-XX:MaxHeapFreeRatio=70 \
-XX:CompressedClassSpaceSize=64m \
-XX:ReservedCodeCacheSize=64m \
-XX:MaxMetaspaceSize=256m \
-Xms256m \
-Xmx750m \

然后,選擇了 *jetty:9-alpine *作為程序的基礎(chǔ)鏡像,畢竟它是 Jetty 中 Java *.wars 最輕量級(jí)的鏡像之一。

最后,既然 600Mb 就夠了,那就得啟動(dòng)一下內(nèi)存限制的docker容器:

docker run -m 600m

轉(zhuǎn)折來了!

由于內(nèi)存不足,啟動(dòng)以后,容器就被 DD(Docker 守護(hù)進(jìn)程)殺死了

很奇怪,** 畢竟這個(gè)容器已經(jīng) 使用完全相同的參數(shù)**在本地啟動(dòng)。

于是我們通過逐步增加容器的內(nèi)存限制,我們達(dá)到了850Mb。

為什么?百思不得其解!

于是到處找了一些文章,看來別人的一些案例以后,我們決定進(jìn)行了一些分析,嘗試定位問題。

結(jié)果爭(zhēng)議更大了!

  • 堆大小與我們之前的(本地)啟動(dòng)相同:


    selection-007.png
  • 但 Docker 顯示了一些瘋狂的統(tǒng)計(jì)數(shù)據(jù):


    selection-010.png

why?問題越來越多了!

我們花了很多時(shí)間為這些有爭(zhēng)議的數(shù)字尋找解釋,發(fā)現(xiàn)并不是只有我們遇到了類似問題。

在閱讀了更多文章,并使用 Native Memory Tracker 分析了程序以后,得出一個(gè)結(jié)論:

原來大多數(shù)額外內(nèi)存已用于存儲(chǔ)已編譯的類及其元數(shù)據(jù)。你也許會(huì)問那JavaVM/Docker 統(tǒng)計(jì)數(shù)據(jù)呢?事實(shí)證明,Java VisualVM 對(duì) OffHeap 一無所知,因此,使用此工具調(diào)查 Java 應(yīng)用程序的內(nèi)存消耗可能完全沒用。

此外,了解你設(shè)置的 JVM 參數(shù)配置也很重要。我們發(fā)現(xiàn)雖然指定 - Xmx=512m告訴 JVM 分配一個(gè)512mb的堆,但是它并沒有告訴 JVM 將其整個(gè)內(nèi)存使用量限制為512mb。

有代碼緩存和各種其他堆外數(shù)據(jù)時(shí),要指定總內(nèi)存,您應(yīng)該使用 -XX:MaxRAM參數(shù)。請(qǐng)注意,在MaxRam=512m時(shí),您的堆大約為 250mb。小心并注意您的應(yīng)用程序 JVM 選項(xiàng)。

于是我們繼續(xù)深入,尋找解決方案:

NMT 和 Java VisualVM Memory Sampler 幫助我們發(fā)現(xiàn)我們的內(nèi)部核心框架在內(nèi)存中被多次復(fù)制為依賴項(xiàng)。重復(fù)的數(shù)量等于我們微服務(wù)中子模塊的數(shù)量。為了更好地理解這一點(diǎn),我想說明我們的“微服務(wù)”結(jié)構(gòu):


microservice.png

這是 NMT(在我的本地機(jī)器上)為一個(gè)模塊(加載了73MB的類元數(shù)據(jù)、42MB線程和37MB的代碼,包括庫)的快照:

7.png

據(jù)我們所知,以這種方式構(gòu)建我們的程序是一個(gè)大錯(cuò)誤。首先,每個(gè) *.war 都作為一個(gè)單獨(dú)的應(yīng)用程序部署在一個(gè) Jetty servlet 容器中,這很奇怪,首先根據(jù)定義,微服務(wù)應(yīng)該是一個(gè)用于部署的應(yīng)用程序(部署單元)。

其次,Jetty 在內(nèi)存中分別保存每個(gè) *.war 所需的所有庫,即使所有這些庫都具有相同的版本。結(jié)果,數(shù)據(jù)庫連接、核心框架中的各種基本功能等都在內(nèi)存中復(fù)制。

一個(gè)常識(shí)性的解決方案是重構(gòu)并使我們的應(yīng)用程序成為真正的微服務(wù)。此外,我們懷疑我們是否需要一整包 Jetty,更何況網(wǎng)上都在警告:

“不要在 Jetty 中部署你的程序,要在你的程序中部署 Jetty?!?/p>

我們決定嘗試使用嵌入式 Jetty 的 Spring Boot,因?yàn)樗坪跏仟?dú)立應(yīng)用程序最常用的工具,尤其是在我們的案例中。很少的配置,沒有 XML,每個(gè) Spring 框架的優(yōu)勢(shì)和很多(很多)插件,它們會(huì)自動(dòng)配置自己。有大量實(shí)用教程和文章展示了如何在互聯(lián)網(wǎng)上使用它。

此外,由于我們不再需要單獨(dú)的 Jetty 應(yīng)用程序服務(wù)器,我們將基礎(chǔ) Docker 映像更改為簡(jiǎn)單的輕量級(jí)OpenJDK。

openjdk:8-jre-alpine

然后,我們根據(jù)新的需求重構(gòu)了我們的應(yīng)用程序。在一天結(jié)束時(shí),我們得到了類似的東西:


improved.png

既然有了想法,那就去試!

問題解決了么?

讓我們從我們的 Java VirtualVM 進(jìn)行測(cè)量。


selection-003.png

selection-005.png
selection-004.png

唔??雌饋砦覀冏隽艘恍└倪M(jìn),但與以前版本的應(yīng)用程序的所有努力和結(jié)果相比,并沒有那么大:

table.png

但是讓我們看一下 Docker 統(tǒng)計(jì)數(shù)據(jù):


selection-006.png

萬歲!我們的內(nèi)存消耗減半。

結(jié)論

這對(duì)我們的團(tuán)隊(duì)來說是一個(gè)很好且有趣的挑戰(zhàn)。試圖找出問題的根因可以讓你發(fā)現(xiàn)真相,并讓你找到特定領(lǐng)域的知識(shí)盲區(qū)。

同時(shí)要相信社區(qū)的力量,不要重復(fù)造輪子,你遇到的問題別人也遇到過!你可以在社區(qū)找到各類案例和答案。另外,不要完全相信來自 Java VisualVM 的內(nèi)存消耗估計(jì),哈哈。

更多案例:
又發(fā)現(xiàn)一個(gè)導(dǎo)致JVM物理內(nèi)存消耗大的Bug(已提交Patch)

一文深度分析Java內(nèi)存模型

一次 Java 內(nèi)存泄漏排查過程,漲姿勢(shì)

小心踩雷,一次Java內(nèi)存泄漏排查實(shí)戰(zhàn)

分析和解決JAVA 內(nèi)存泄露的實(shí)戰(zhàn)例子

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容