測試和優(yōu)化Java應用程序的內存使用,你學會了嗎?
垃圾收集器日志文件可以幫助定位代碼中的問題,并確定服務器或虛擬環(huán)境的正確尺寸。
譯自Test and Optimize Your Java Application's Memory Use,作者 Frank Delporte。
確定運行 Java 應用程序的理想內存大小可能非常困難。但隨著云實例成本和生態(tài)影響的不斷上升,正確地調整機器尺寸以處理預期負載而不過度調整非常重要,這樣可以最大程度地降低機器成本,同時減少其生態(tài)影響。了解應用程序的內存大小需求對于以最低運營成本實現最高性能至關重要。
我將向您展示如何使用垃圾收集器 (GC) 日志文件來確定應用程序所需的內存大小。借助Java運行時,我們可以依靠 GC 來清理不再使用的內存,并盡可能降低總內存量。在此過程中,GC 可以輸出包含大量信息的日志文件,這些信息可以幫助我們找到代碼中的問題并為我們的服務器或虛擬環(huán)境定義正確的尺寸。
如何測試您的應用程序
對您的應用程序進行現實世界測試中最難但最重要的部分是擁有可重復的負載模擬,該模擬類似于應用程序的實際使用情況。這是開發(fā)和部署應用程序的重要步驟,需要您的開發(fā)和 DevOps 團隊之間的合作。
您希望從這樣的測試中了解一些重要的結果:定義應用程序所需的內存量并測試最大吞吐量。以下是一些實現此目標的指南:
- 慢慢來: 當執(zhí)行 Java 應用程序時,JVM 會將最常用的字節(jié)碼(類文件)重新編譯為本地代碼。此過程需要一些時間(稱為預熱時間),因此您需要等待應用程序在您期望的典型負載下運行足夠長的時間。這意味著所有執(zhí)行的代碼都已由施加在應用程序上的負載調用。
- 注意本地測試: 一些測試可以輕松地在您自己的機器上執(zhí)行,但請注意測試本身的負載。在運行應用程序的同一臺機器上執(zhí)行負載測試會導致 CPU 和/或內存過載,從而影響測試中應用程序的性能。
- 使用現實世界測試: 只有當您可以在類似于生產系統的環(huán)境中模擬預期負載時,測試才有效。
- 在生產環(huán)境中測試: GC 日志對系統性能的影響很小。在許多情況下,與設置完整的測試環(huán)境相比,這將是獲取真實日志結果的更輕松、更便宜的解決方案。
使用 Spring PetClinic 進行實驗
我使用 Spring PetClinic 應用程序來收集本文的測試結果。源代碼可在 GitHub 上獲得,其中包括 JMeter 測試腳本。
運行測試應用程序
要遵循此方法,請獲取源代碼,編譯應用程序并使用以下命令啟動它:
# Get the sources
$ git clone https://github.com/spring-projects/spring-petclinic
$ cd spring-petclinic
# Generate a JAR and run it. Use CTRL+C to stop the application
$ ./mvnw package
$ java -Xlog:gc,safepoint:gc.log::filecount=0 -jar target/*.jar
您的應用程序現在已配置為將垃圾收集日志存儲在一個文件中。此設置非常適合此測試。但在生產環(huán)境中啟用 GC 日志時,您應該使用滾動文件以防止文件變得太大并填滿存儲空間。例如,使用-Xlog:gc,safepoint:gc.log::filecount=10,filesize=100M將日志輪換設置為最多 10 個文件,每個文件 100MB。當您未定義文件數量和文件大小時,默認值為 5 個文件,每個文件 20MB,因此 GC 日志不會使用超過 100MB 的空間。
關于 JMeter
Spring PetClinic 項目包含一個 JMeter 測試??梢允褂?Apache JMeter 執(zhí)行此類測試,Apache JMeter 是一個 100% 純開源 Java 應用程序,旨在對功能行為進行負載測試并衡量性能。它最初是為測試 Web 應用程序而設計的,但后來擴展到其他測試功能。查看最新版本(在jmeter.apache.org/download_jmeter.cgi上)并下載它。
$ cd ~/Downloads
$ wget https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.6.3.zip
$ unzip apache-jmeter-5.6.3.zip
$ rm apache-jmeter-5.6.3.zip
JMeter 測試可以使用 GUI 應用程序執(zhí)行,但不建議這樣做,因為它會帶來 GUI 影響測試性能的風險。GUI 僅應用于創(chuàng)建測試或運行測試以驗證其配置。
使用 JMeter GUI 創(chuàng)建測試
啟動 Apache JMeter GUI 應用程序:
$ java -jar ~/Downloads/apache-jmeter-5.6.3/bin/ApacheJMeter.jar
- 在 UI 中,單擊文件 > 打開,然后選擇文件spring-petclinic/src/test/jmeter/petclinic_test_plan.jmx。
- 您可以通過點擊開始按鈕來執(zhí)行測試以驗證配置,這將啟動線程來模擬 500 個用戶。
- 運行測試直到測試完成?;顒泳€程數將從 500 降至 0。
圖片
圖片
圖片
使用 JMeter 在無頭模式下運行負載測試
對于實際測試,我們將以無頭模式執(zhí)行 JMeter。在我的情況下,我在運行應用程序的同一臺機器上執(zhí)行測試,因為它有足夠的內存和 CPU 來處理兩者。使用相同方法時,您需要確保這對于您的測試有效。
讓我們運行一個測試并使用以下選項生成報告:
- -n: 在無頭模式下運行(無 GUI)
- -t: 要執(zhí)行的.jmx測試腳本的路徑
- -l: 用于存儲原始結果的.jtl文件的路徑
- -o: 負載測試后生成報告儀表板的輸出文件夾的路徑,該文件夾必須為空目錄
- -e: 負載測試后生成報告儀表板
$ java -jar ApacheJMeter.jar -n -t spring-petclinic/src/test/jmeter/petclinic_test_plan.jmx -l jmeter.jtl -o jmeter-report/ -e
當您不添加 -e 選項時,您仍然可以根據測試運行期間創(chuàng)建的.jtl文件稍后生成 HTML 報告。
- -g: 測試期間生成的.jtl文件的路徑
- -o: 用于存儲 HTML 報告的文件夾
$ java -jar ApacheJMeter.jar -g jmeter.jtl -o jmeter-report/
由于每個新的 Java 運行時版本都帶來了性能改進,因此了解您的生產系統使用哪個版本非常重要。我使用 Azul Zulu Builds of OpenJDK 版本 21.0.3 執(zhí)行了我的測試。
$ java -version
#openjdk version "21.0.3" 2024-04-16 LTS
#OpenJDK Runtime Environment Zulu21.34+19-CA (build 21.0.3+9-LTS)
#OpenJDK 64-Bit Server VM Zulu21.34+19-CA (build 21.0.3+9-LTS, mixed mode, sharing)
閱讀 JMeter 報告
在 JMeter HTML 報告目錄(在我的情況下為jmeter-report/,如 -o 參數指定)中,您可以找到包含 JMeter 測試結果的網頁。您不會在這里找到任何與內存相關的信息,但會找到 JMeter 測試文件中定義的測試結果。例如:響應時間百分位數、每秒命中數的吞吐量等。
圖片
圖片
檢查 GC 日志結果
gc.log文件是了解應用程序內存使用情況的“最佳位置”。使用 Azul GC Log Analyzer,我們可以讀取此文件并可視化一段時間(掛鐘時間和正常運行時間)內的一組圖表,以檢查垃圾收集器、JIT(即時)編譯器、系統指標等。以下圖表顯示,垃圾收集器暫停持續(xù)時間在初始負載后保持在 10 毫秒以下,垃圾收集后的堆大小保持在 64MB 左右。我們建議您使用該值的雙倍來確定系統尺寸。因此,在這種情況下,應用程序將能夠處理與測試期間生成的相同負載,內存為 128MB。
圖片
圖片
您可以對您的應用程序遵循相同的原則,并在更改 Java 運行時的–Xmx設置或虛擬環(huán)境的內存配置后重新檢查暫停持續(xù)時間和堆使用情況。
Azul Zing 和 Zulu Builds of OpenJDK 之間的 GC 日志差異
通過不同的內部基準測試,我們創(chuàng)建了一些額外的日志文件來演示 Azul Zulu 和 Zing Builds of OpenJDK 版本 17 提供的不同結果。
使用 Zulu 的結果
當我們使用 Zulu(OpenJDK 的一個版本)生成 GC 日志時,我們在日志文件中獲得與大多數其他發(fā)行版相同的數據。以下圖表顯示,垃圾收集器暫停持續(xù)時間保持在 80 毫秒以下,垃圾收集后堆利用率保持在舊一代的 1GB 左右(用于長期對象)和新一代的 2GB 左右(用于臨時對象)。
在這個特定的測試用例中,總共-Xmx4G足夠并且實際上被使用,但通常標準建議是將-Xmx設置為觀察到的堆利用率的兩倍;在這里,它將是-Xmx6G。
圖片
圖片
圖片
使用 Zing 的結果
我們使用 Zing 重復了相同的測試,Zing 是一種基于 OpenJDK 的替代 Java 運行時,但它具有更好的 JIT 編譯器(Falcon)和額外的垃圾收集器(C4,持續(xù)并發(fā)壓縮收集器)。
由于 C4 垃圾收集器提供的額外信息,圖表看起來略有不同。使用并發(fā) GC 時,GC 在與應用程序并行活動時的并發(fā)持續(xù)時間是一個更重要的指標。它不會暫停應用程序,但會消耗一些 CPU 時間。100% 并不意味著它消耗了所有 CPU 時間的 100%,因為基準 100% 是 GC 線程的總數,這少于 CPU 內核的數量。但應通過增加堆大小來避免長時間保持在 100%。大多數時間通常由 GC 用于處理臨時對象。在這里,在這個特定的測試用例中,與具有相同-Xmx4G的 Zulu 相比,應用程序性能在 Zing 中仍然更好。
對于一般大小調整,Zing 的 Live Set 圖表也很重要,因為它顯示了活動對象的數量,例如,不包括未引用的對象,也稱為垃圾。
圖片
圖片
圖片
結論
垃圾收集器日志提供了檢查應用程序需要多少內存的正確指標。能夠在與生產系統相同的環(huán)境中,以類似的負載測試應用程序至關重要。也許“在生產環(huán)境中測試”可能是實現這一目標的最簡單方法。
YOUTUBE.COM/THENEWSTACK技術發(fā)展迅速,不要錯過任何一集。訂閱我們的 YouTube 頻道以收看我們所有的播客、訪談、演示等。