Try   HackMD

Minimize Java Memory Usage with the Right Garbage Collector

在軟體方面,尺寸很重要。 很明顯,與大型整體方法相比,在微服務架構中使用小塊可以提供更多優勢。 最近的 Jigsaw Java 版本有助於分解遺留應用程序或從頭開始構建新的雲原生應用程序。

這種方法減少了磁碟空間,構建時間和啟動時間。 但是,它對 RAM 使用管理沒有幫助。 眾所周知,Java 在許多情況下消耗大量記憶體。 與此同時,許多人沒有注意到 Java 在記憶體使用方面變得更加靈活,並提供了滿足微服務要求的功能。 在本文中,我們將分享我們的經驗,如何在 Java process 中調整 RAM 使用率,使其更具彈性,並獲得更快擴展和降低總體擁有成本(TCO)的好處。

縮放有三種選擇:垂直,水平和兩者的組合。 為了最大化結果,首先需要以最佳方式設置垂直縮放。 然後,當您的項目水平增長時,單個容器中預配置的資源消耗將復製到每個實例,效率將按比例增長。

如果配置正確,垂直縮放適用於微服務和整體,根據容器內的當前負載優化內存和CPU使用。 選定的垃圾收集器是主要的基礎磚之一,其設置可以影響整個項目。

OpenJDK有五種廣泛使用的垃圾收集器解決方案:

  • G1
  • Parallel
  • ConcMarkSweep (CMS)
  • Serial
  • Shenandoah

讓我們看看每個這些在縮放方面的表現以及可以應用哪些設置來改善結果。

為了進行測試,我們將使用一個示例 Java 應用程序來幫助跟踪 JVM 垂直擴展結果:https://github.com/jelastic/java-vertical-scaling-test

將為每個 GC 測試啟動以下 JVM 啟動選項:

java -XX:+Use[gc_name]GC -Xmx2g -Xms32m -jar app.jar [sleep]

參數:

  • [gc_name]將替換為特定的垃圾收集器類型
  • Xms 縮放步驟(scaling step)(在我們的例子中是32 MB)
  • Xmx 是最大擴展限制(在我們的示例中為2 GB)
  • [sleep] 是記憶體加載週期之間的間隔,以毫秒為單位,默認為10

目前,需要調用 Full GC 才能正確釋放未使用的資源。 它可以通過各種選項輕鬆啟動:

  • jcmd <pid> GC.run - 執行外部呼叫
  • System.gc()inside the source code
  • jvisualvm - 手動通過 great VisualVM 故障排除工具
  • -javaagent:agent.jar - 可插拔的常用方法。 Github repo Java Memory Agent 提供了開源自動化附加組件。

可以在輸出日誌中跟踪記憶體使用情況,也可以使用 VisualVM 進行更深入的查看。

G1 Garbage Collector

Java 生態系統的好消息是,從 JDK 9 開始,默認情況下啟用現代縮小的 G1 垃圾收集器。 如果使用較低版本的 JDK,可以使用 -XX:+ UseG1GC 參數啟用 G1。

G1 的主要優勢之一是能夠在沒有冗長的暫停時間的情況下壓縮可用內存空間並且不使用未使用的 heap。 我們發現此 GC 是在 OpenJDK 或 HotSpot JDK 上運行的 Java 應用程序的垂直擴展的最佳選擇。

為了更好地理解 JVM 在不同內存壓力水平下的表現,我們將運行三種情況:1)快速,2)中等,以及3)緩慢的內存使用增長。 通過這種方式,我們可以檢查智能 G1 的人機工程學以及 GC 如何處理不同的內存使用動態。

快速記憶體使用量增長

java -XX:+UseG1GC -Xmx2g -Xms32m -jar app.jar 0

記憶體在 25 秒內從 32 MiB 增加到 1 GiB。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

如果記憶體使用量增長非常快,則 JVM 符合人體工程學會忽略 Xms 擴展步驟,並根據其內部自適應優化算法更快地保留 RAM。 因此,相對於快速實際使用(藍色)增長,我們看到 JVM(橙色)的 RAM 分配要快得多。 因此,對於 G1,即使出現負載峰值,我們也是安全的。

中等記憶體使用量增長

java -XX:+UseG1GC -Xmx2g -Xms32m -jar app.jar 10

在 4 個週期內,記憶體在 90 秒內從 32MiB 增長到 1 GiB

有時,JVM 符合人體工程學需要幾個週期才能找到最佳的 RAM 分配算法。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

正如我們所看到的,JVM 在第 4 個週期調整了RAM 配置符合人體工程學,使得垂直縮放對於可重複的循環非常有效。

記憶體使用量增長緩慢

java -XX:+UseG1GC -Xmx2g -Xms32m -jar app.jar 100

內存從 32 MiB 增長到 1 GiB,增量時間增長約 300 秒。 非常有彈性和高效的資源擴展,符合我們的期望 - 令人印象深刻。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

正如您所看到的那樣,橙色區域(保留 RAM)會隨著藍色區域(實際使用情況)的增長而緩慢增加。所以沒有過度使用或不必要的保留內存。

重要:積極的 heap 或垂直縮放

用於提高 Java 應用程序性能的常用 JVM 配置通常會妨礙垂直有效擴展的能力。 因此,您需要在應用程序最重要的屬性的優先級之間進行選擇。

許多廣泛使用的設置之一是啟用 Aggressive Heap 以嘗試最大限度地利用堆的物理記憶體。 讓我們分析使用此配置時會發生什麼。

java -XX:+UseG1GC -Xmx2g -Xms2g

java -XX:+UseG1GC -Xmx2g -XX:+AggressiveHeap

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

我們可以看到,保留堆(橙色)是常量,並且不會隨時間發生變化,因此容器中沒有 JVM 的垂直縮放。 即使您的應用程序僅使用可用 RAM 的一小部分(藍色),其餘的也無法與其他進程或其他容器共享,因為它已完全分配給 JVM。

因此,如果要垂直擴展應用程序,請確保未啟用主動 heap(參數應為-XX:-AggressiveHeap),也不要將 -Xms 定義為 -Xmx(例如,不要指定 -Xmx2g -Xms2g)。

平行垃圾收集器

Parallel 是高吞吐量 GC,默認情況下在 JDK8 中使用。 同時,它不適合內存收縮,因此不適合靈活的垂直縮放。 為了確認這一點,讓我們使用我們的示例應用程序運行測試:

java -XX:+UseParallelGC -Xmx2g -Xms32m -jar app.jar 10

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

我們可以看到,未使用的 RAM 不會釋放回操作系統。 帶有並行 GC 的 JVM 可以永久保留它,甚至可以忽略顯式的 Full GC 調用。

因此,如果您希望根據應用程序負載從垂直擴展中受益,請將 Parallel 更改為 JDK 中可用的收縮 GC。 它將所有活動對像打包在一起,刪除垃圾對象,並取消提交並將未使用的內存釋放回操作系統。

Serial 和 ConcMarkSweep 垃圾收集器

Serial 和 ConcMarkSweep 也在縮小垃圾收集器,可以在 JVM 中垂直擴展記憶體使用。 但與 G1 相比,它們需要 4 個完整 GC 週期才能釋放所有未使用的資源。

讓我們看看這兩個垃圾收集器的測試結果:

java -XX:+UseSerialGC -Xmx2g -Xms32m -jar app.jar 10

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

java -XX:+UseConcMarkSweepGC -Xmx2g -Xms32m -jar app.jar 10

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

從 JDK9 開始,可以使用新的 JVM 選項 -XX 來加速內存的釋放:-ShrinkHeapInSteps 在第一個完整 GC 循環之後立即關閉已提交的RAM。

Shenandoah 垃圾收集器

Shenandoah 是垃圾收集器中的後起之秀,已經可以被認為是 JVM 垂直擴展的最佳解決方案。

與其他產品相比的主要區別在於能夠異步收縮(不使用和釋放未使用的 RAM 到 OS)而無需調用 Full GC。 在檢測到空閒內存後,Shenandoah 幾乎可以立即壓縮活動對象,清理垃圾並將 RAM 釋放回操作系統。 省略 Full GC 的可能性導致消除相關的性能下降。

讓我們看看它在實踐中是如何運作的:

java -XX:+UseShenandoahGC -Xmx2g -Xms32m -XX:+UnlockExperimentalVMOptions -XX:ShenandoahUncommitDelay=1000 -XX:ShenandoahGuaranteedGCInterval=10000 -jar app.jar 10

在這裡,我們在 Shenandoah 中添加了一些額外的參數:

  • -XX:+ UnlockExperimentalVMOptions - 需要啟用下面列出的uncommit選項
  • -XX:ShenandoahUncommitDelay = 1000 - 垃圾收集器將啟動未提交超過此時間(以毫秒為單位)使用的內存。 請注意,當應用程序想要恢復內存時,使延遲太低可能會引入分配停頓。 在實際部署中,建議延遲大於 1 秒
  • -XX:ShenandoahGuaranteedGCInterval = 10000 - 這保證了在規定的時間間隔內的 GC 循環(以毫秒為單位)

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Shenandoah 非常有彈性,只分配必要的資源。 它還壓縮使用過的RAM(藍色)並將未使用的保留RAM(橙色)釋放回操作系統,無需昂貴的全GC調用。 請注意,此GC是實驗性的,因此您對穩定性的反饋將對其創建者有所幫助。

結論

Java 不斷完善和適應不斷變化的需求。 因此,目前其 RAM 的胃口不再是傳統應用程序的微服務和雲託管的問題,因為已經有正確的工具和方法來正確地擴展它,清理垃圾並釋放所需進程的資源。 通過智能配置,Java 可以在所有項目範圍內實現經濟高效 - 從雲原生初創公司到傳統企業應用程序。

Reference

Minimize Java Memory Usage with the Right Garbage Collector