閱讀128 返回首頁    go 阿裏雲 go 技術社區[雲棲]


實例解讀:如何減少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值找到它:

20170818040405610.jpg20170818040405504.jpg20170818040405238.jpg

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

所以,我們得出結果:大概600Mb就夠了。我們選擇了一個t2.micro AWS實例(具有1Gb RAM)進行部署,並開始部署應用程序。首先,通過JVM選項提供有關內存配置的一些信息:

如何減少Docker中的Java內存消耗

此外,作為應用程序的基礎圖像,我們選擇了 ,因為我們發現它是 Jetty Java *.wars中最輕量級的圖像之一。

正如前文所提到的600Mb就足夠了,所以啟動了一個容器,其容量如下:

docker run -m 600m

那麼你覺得這樣會發生什麼?我們的集裝箱會由於內存不足被DD(Docker daemon)殺死。這個容器是在本地啟動的,具有完全相同的參數,通過逐步增加容器的內存限製,我們達到了700…我開玩笑的,我們有850mb。

經過觀察和閱讀有用的文章,我們決定進行測量。結果是非常奇怪和有爭議的。

Heap Size與我們本地相同:

20170818040405150.jpg

  但Docker顯示了一些瘋狂的統計數據:

如何減少Docker中的Java內存消耗

  發生了什麼事?情況變得非常混亂...

我們花了很多時間去搞清楚這些數據為什麼會有爭議,在搜索過程中發現我們並不是個例。在查找了很多資料,分析了 應用之後,我們幾乎找到了答案。大部分額外的內存都是被用於存儲編譯的類及其元數據。

那麼有關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 可以幫助我們發現內部核心框架在內存中多次重寫的依賴,並且重複的數量等於微服務中子模塊的數量。如果想要更好的掌握這一點,首先要說明一下我們的“微服務”結構:

20170818040405369.jpg

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

20170818040405955.jpg

據我所知,構建這樣的應用程序是一個很大的錯誤。首先,每個* .war已經作為一個單獨的應用程序部署在Jetty servlet容器中,這是非常奇怪的。因為根據定義,微服務應該是一個部署應用程序(部署單元)。其次,Jetty在內存中分別保存每個* .war的所有必需庫,即使所有這些庫具有相同的版本。因此,DB連接、核心框架的各種基本功能等都會在內存中重複。

一般的解決方案就是重構,讓應用成為真正的微服務器。此外,我們可能還需要一整套的Jetty,但是一般人聽到它的報價就能望而卻步。圈內有句著名的評論“不要在Jetty中部署應用程序,而是要在應用程序中部署Jetty。

我們決定嚐試使用嵌入式Jetty的Spring Boot,因為它是獨立應用程序中最常用的工具,尤其是在我們的案例中。配置很少,沒有XML,每個Spring框架都有優勢,還有很多插件,自動配置。除此之外,網上還有大量的實用教程和文章,這些都讓Spring Boot看起來是個不錯的選擇。

由於我們不再需要單獨的Jetty應用程序服務器,所以我們將基礎Docker的圖像更改為輕量級的openjdk。

openjdk:8-jre-alpine

根據新的要求重構了應用程序之後,我們得到了類似下圖的東西:

20170818040405422.jpg

  現在一切都準備好了,我們來測試一下。

先從Java VirtualVM進行測試:

20170818040405569.jpg20170818040405833.jpg20170818040406783.jpg

從數據中可以看出,雖然新版本有了一些改進,但是與之前的應用程序相比變化並沒有那麼大:

如何減少Docker中的Java內存消耗

  然後,我們再來看看Docker的統計數據:

如何減少Docker中的Java內存消耗

  結果很明顯,我們已經減少了內存消耗。

結論

對我們團隊來說,這是一個很有意思的挑戰。找到導致錯誤或者某件事情的根本原因,會讓你在特定領域中看得更深更廣。網上的資源非常豐富,如果你下定決心去查找答案,那麼就一定會找得到。另外,從這次事件中也認識到,不要完全信任Java VisualVM的內存消耗預計。

對於技術的學習和性能的提高,我有三個更多和大家分享,閱讀更多,改進更多,調查更多。另外,避免常規,盡可能自動化,這樣你可以更有效的進行工作。


本文轉自d1net(轉載)

最後更新:2017-08-22 11:02:57

  上一篇:go  盤點2017年熱度很高的編程語言
  下一篇:go  智能家居如何讓消費者心甘情願地買單