OpenJ9 和 HotSpot 的對比 Part 1

是來自默認 Oracle HotSpot JVM 的不同 JVM 實現。使用現代的 adoptopenjdk 預置 Docker 鏡像,你可以輕易地切換和測試不同的組合,並且可以為你選擇合適的 JVM。

這個傳言看起來是真的,OpenJ9 在內存使用方面看起來已經領先 HotSpot 了。HotSpot 似乎在 CPU 方面具有優勢。

OpenJ9

在 Java 的世界中,大多人都熟悉 OpenJDK。這是一個完全的 JDK 實現,包括對 HotSpot JVM 引擎的實現。不是很多開發者瞭解或嘗試選擇 HotSpot。詢問周圍的同事後,他們都記得 JRockit 這個名字,但沒有人提起 IBM J9 及(或) Eclipse OpenJ9。

我已經瞭解到了 OpenJ9 擅長於內存管理,而且在雲/容器中的使用上已經經過了精簡。OpenJ9 是一個獨立的對 JVM 的實現。它源於 IBM 的 Java SDK/IBM J9,它的歷史能追溯到 OTI Technologies Envy Smalltalk(感謝 Dan Heidinga!)。

隨著微服務使用率的提升(而且 Java 中的大多數服務都不是特別小)。我認為它將會再次變成一個熱門話題!

測試

在 Docker 時代之前,比較不同的 JVM 版本是相對困難的。你需要下載、安裝、編寫腳本和運行所有相關項。但現在很多提前製作好的鏡像可以通過在線獲得。

這裡是我關於如何測試 JVM 的想法:

  1. 創建一個簡單的 Spring Boot 應用程序
  2. 在各種 Docker 鏡像中啟動該應用程序
  3. 在啟動和 GC 後測量內存使用情況
  4. 測量運行小型 CPU 密集型測試所需的時間

這絕不是一個徹底的測試或基準測試,但它應該能給我們一個我們可以從虛擬機中獲得什麼的大致想法。

Spring Boot 應用

我創建的 Spring Boot 應用包含了下面的端點:

  1. 一個 REST 端點調用 GC(儘量讓它合理)
  2. 一個 REST 端點創建了 1000 個大型隨機數組並對其排序,返回運行時長(單位為 ms)

下面列出了 CPU 測試:

@RestControllerpublic class LoadTestController { @RequestMapping("/loadtest") public LoadTestResult loadtest() { long before = System.currentTimeMillis(); Random random = new Random(); for(int i = 0; i < 1000; i++) { long[] data = new long[1000000]; for(int l = 0; l < data.length; l++) { data[l] = random.nextLong(); } Arrays.sort(data); } return new LoadTestResult(System.currentTimeMillis() - before); }}

就這個測試是否合理和切題…我們可以無休止地爭論,但是對於可以期待它是什麼樣的表現形式,它能給我們一些基本概念。如果傳言中的內存提升是真的,可能會出現性能干擾嗎?是否存在性能折中的情況?

JVM 鏡像

我決定對以下鏡像進行測試。

首先我們有 8/9/10/11 版本的(輕量級)openjdk 鏡像:

  • openjdk:8-slim
  • openjdk:9-slim
  • openjdk:10-slim
  • openjdk:11-slim

接下來是 8/9/10 版本的 adoptopenjdk 鏡像:

  • adoptopenjdk/openjdk8
  • adoptopenjdk/openjdk9
  • adoptopenjdk/openjdk10

之後是 OpenJ9,也是由 adoptopenjdk 為 8/9 版本所提供的,同時包含為 9 發佈的每日構建版(相關內容請查看我之前的博文):

  • adoptopenjdk/openjdk8-openj9
  • adoptopenjdk/openjdk9-openj9
  • adoptopenjdk/openjdk9-openj9:nightly

同時我決定也引入 IBM 自家的 J9 鏡像:

  • ibmcom/ibmjava:8-jre

使用 Docker 進行測試

在構建我的 Spring Boot 應用之後,我使用下面命令啟動了每個 Docker 鏡像:

docker run -it -v /Projects/temp/spring-boot-example:/app/spring-boot-example -p 8080:8080 IMAGE_NAME /bin/bash

我將 "spring-boot-example" 項目目錄映射到了 "/apps/spring-boot-example",然後我就可以啟動容器中的 JAR 文件。同時我將端口 8080 轉發到我的主機,這樣我就可以調用這些端點。

下一步,在容器內部,我啟動了 Spring Boot 應用:

java -jar /app/spring-boot-example/target/spring-boot-example-0.0.1-SNAPSHOT.jar

等待一段時間,調用幾次端點,並執行一個 GC,之後我測量內存使用情況。

然後,我調用了包含數組排序測試的 "/loadtest" 端點,然後等待結果.

內存基準測試

下面是示例的 Spring Boot 應用的內存使用結果:

OpenJ9 和 HotSpot 的對比 Part 1

首先你可以看到 Java 8 的內存使用明顯高於 Java 9 和 10。

但是令人最吃驚的是 OpenJ9 和 J9 使用的內存減少的量,如果將 Java8 和 OpenJ9 對比,後者幾乎是前者的 4 倍。我很驚奇,這是怎麼形成的?現在我們幾乎可以將 Spring Boot 服務稱為微服務 (micro) 了!

我也嘗試過運行一些生產環境中的 Spring Boot 代碼(不僅僅是簡單的示例),通過它們我看到了內存使用量減少了 40-50% 的改進。

CPU 基準測試

我已經瞭解到,如果您從 CPU 密集任務方面去對比,OpenJ9 不如 HotSpot。這就是為什麼我為此創建了一個小測試。

給具有 1000000 個隨機 long 值的 1000 個數組排序。這大約需要花費 100 秒,這應該給了 JVM 足夠的時間去調整和優化。我已經為每個測試鏡像調用了兩次基準測試。我記錄了第二次的時間以嘗試去排除熱啟動的時間。

OpenJ9 和 HotSpot 的對比 Part 1

從圖表中我們能看到 J9 和 OpenJ9 的鏡像確實很慢,最快不超過 18%。從這個特定測試用例看來,Java 8 擊敗了絕大多數 Java 9 的實現(除了 OpenJ9 以外)。

下一步是什麼?

在生產環境中,與內存問題相比,我當前的項目存在更多的 CPU 問題(頻繁運行過程中的內存溢出,而 CPU 使用率是 1-2%)。在不久的將來,我們肯定會切換至使用 OpenJ9。

測試期間,我們也確實遇到了一些問題:

  1. Hessian:(二進制協議)有一個內置的假設,即 System.identityHashCode 總是返回一個正數。對於 HotSpot 來說這是個事實,但是 OpenJ9/J9 也可以返回負數。這是個公開的問題,Hessian 項目在近幾年內並沒有解決這個問題,似乎它已經停止維護了?所以我們的解決方案是遠離 Hessian。
  2. Instana:我們熱愛我們的監控工具 Instana,但是它連接代理到 OpenJ9/J9 的時候出了些問題。幸運的是,Instana 的開發者幫助我們確認了一個錯誤,還進行了修復(並且會自動更新,哇!)

我沒有注意到的開放性問題:

  1. 你仍然可以在 OpenJ9 中對信息進行 get/use jmap/hprof 等操作嗎?
  2. 它將如何在更長久的生產環境中保持運行?
  3. 我們會發現其他的奇怪錯誤嗎?感覺很棘手…

你嘗試過使用 OpenJ9/J9 嗎?在評論區中和大家分享你的看法吧!


分享到:


相關文章: