實例解讀:如何減少Docker中的Java內存消耗
最近,我所在的團隊麵臨著部署微服務(Java+SpringMVC in Docker on AWS)的問題。主要問題是,很多非常輕巧的應用程序消耗了太多的內存。因此,我們經過多方嚐試找到了在Docker中關於Java內存消耗的問題,並通過重構和遷移到Spring Boot實現了減少消耗的方法。本文,我將和大家分享這整個過程,希望能夠對大家有所幫助。
在部署之前,我們要估計應用程序消耗多少內存。為此,我們製定了一個清晰簡單的方程來找到RSS:
RSS = Heap size + MetaSpace + OffHeap size
這裏OffHeap由線程堆棧,緩衝區,庫(* .jars)和JVM代碼組成。
Resident Set Size是當前分配給進程使用的RAM的數量。它包括代碼、數據和共享庫。
讓我們根據本地Java VisualVM值找到它:



RSS = 253(Heap) + 100(Metaspace) + 170(OffHeap) + 52*1(Threads) = 600Mb (max avarage)
所以,我們得出結果:大概600Mb就夠了。我們選擇了一個t2.micro AWS實例(具有1Gb RAM)進行部署,並開始部署應用程序。首先,通過JVM選項提供有關內存配置的一些信息:
此外,作為應用程序的基礎圖像,我們選擇了 ,因為我們發現它是 Jetty Java *.wars中最輕量級的圖像之一。
正如前文所提到的600Mb就足夠了,所以啟動了一個容器,其容量如下:
docker run -m 600m
那麼你覺得這樣會發生什麼?我們的集裝箱會由於內存不足被DD(Docker daemon)殺死。這個容器是在本地啟動的,具有完全相同的參數,通過逐步增加容器的內存限製,我們達到了700…我開玩笑的,我們有850mb。
經過觀察和閱讀有用的文章,我們決定進行測量。結果是非常奇怪和有爭議的。
Heap Size與我們本地相同:

但Docker顯示了一些瘋狂的統計數據:
發生了什麼事?情況變得非常混亂...
我們花了很多時間去搞清楚這些數據為什麼會有爭議,在搜索過程中發現我們並不是個例。在查找了很多資料,分析了 應用之後,我們幾乎找到了答案。大部分額外的內存都是被用於存儲編譯的類及其元數據。
那麼有關JavaVM / Docker統計數據的爭議數字呢?事實證明,Java VisualVM不了解OffHeap的內容,因此,使用此工具調查Java應用程序的內存消耗會非常棘手。此外,了解您使用的JVM選項也是至關重要。在我看來,指定-Xmx=512m告訴JVN分配512mb Heap,但是它並不告訴JVM整個內存使用要限製在512mb,所以就會出現代碼緩存和其它非Heap數據占用內存直至超過。所以,要指定總內存需要使用XX:MaxRAM參數。請注意,使用MaxRam = 512m,你的Heap約為250mb,這時就要注意應用程序JVM選項。
星期一早上,尋找靈丹妙藥...
NMT和Java VisualVM Memory Sampler 可以幫助我們發現內部核心框架在內存中多次重寫的依賴,並且重複的數量等於微服務中子模塊的數量。如果想要更好的掌握這一點,首先要說明一下我們的“微服務”結構:

這是NMT(在本地機器上)的一個模塊快照(具有73MB加載的類元數據,42MB線程和37MB的代碼,包括lib):

據我所知,構建這樣的應用程序是一個很大的錯誤。首先,每個* .war已經作為一個單獨的應用程序部署在Jetty servlet容器中,這是非常奇怪的。因為根據定義,微服務應該是一個部署應用程序(部署單元)。其次,Jetty在內存中分別保存每個* .war的所有必需庫,即使所有這些庫具有相同的版本。因此,DB連接、核心框架的各種基本功能等都會在內存中重複。
一般的解決方案就是重構,讓應用成為真正的微服務器。此外,我們可能還需要一整套的Jetty,但是一般人聽到它的報價就能望而卻步。圈內有句著名的評論“不要在Jetty中部署應用程序,而是要在應用程序中部署Jetty。
我們決定嚐試使用嵌入式Jetty的Spring Boot,因為它是獨立應用程序中最常用的工具,尤其是在我們的案例中。配置很少,沒有XML,每個Spring框架都有優勢,還有很多插件,自動配置。除此之外,網上還有大量的實用教程和文章,這些都讓Spring Boot看起來是個不錯的選擇。
由於我們不再需要單獨的Jetty應用程序服務器,所以我們將基礎Docker的圖像更改為輕量級的openjdk。
openjdk:8-jre-alpine
根據新的要求重構了應用程序之後,我們得到了類似下圖的東西:

現在一切都準備好了,我們來測試一下。
先從Java VirtualVM進行測試:



從數據中可以看出,雖然新版本有了一些改進,但是與之前的應用程序相比變化並沒有那麼大:
然後,我們再來看看Docker的統計數據:
結果很明顯,我們已經減少了內存消耗。
結論
對我們團隊來說,這是一個很有意思的挑戰。找到導致錯誤或者某件事情的根本原因,會讓你在特定領域中看得更深更廣。網上的資源非常豐富,如果你下定決心去查找答案,那麼就一定會找得到。另外,從這次事件中也認識到,不要完全信任Java VisualVM的內存消耗預計。
對於技術的學習和性能的提高,我有三個更多和大家分享,閱讀更多,改進更多,調查更多。另外,避免常規,盡可能自動化,這樣你可以更有效的進行工作。
本文轉自d1net(轉載)
最後更新:2017-08-22 11:02:57