java 11 完全支援Linux容器(包括Docker)
許多執行在Java虛擬機器中的應用程式(包括Apache Spark和Kafka等資料服務以及傳統的企業應用程式)都可以在Docker容器中執行。但是在Docker容器中執行Java應用程式一直存在一個問題,那就是在容器中執行JVM程式在設定記憶體大小和CPU使用率後,會導致應用程式的效能下降。這是因為Java應用程式沒有意識到它正在容器中執行。隨著Java 10的釋出,這個問題總算得以解決,JVM現在可以識別由容器控制組(cgroups)設定的約束。可以在容器中使用記憶體和CPU約束來直接管理Java應用程式,其中包括:
遵守容器中設定的記憶體限制
在容器中設定可用的CPU
在容器中設定CPU約束
Java 10的這個改進在Docker for Mac、Docker for Windows以及Docker Enterprise Edition等環境均有效。
容器的記憶體限制
在Java 9之前,JVM無法識別容器使用標誌設定的記憶體限制和CPU限制。而在Java 10中,記憶體限制會自動被識別並強制執行。
Java將伺服器類機定義為具有2個CPU和2GB記憶體,以及預設堆大小為實體記憶體的1/4。例如,Docker企業版安裝設定為2GB記憶體和4個CPU的環境,我們可以比較在這個Docker容器上執行Java 8和Java 10的區別。
首先,對於Java 8:
docker container run -it -m512 --entrypoint bash openjdk:latest
$ docker-java-home/bin/java -XX:+PrintFlagsFinal -version | grep MaxHeapSize
uintx MaxHeapSize := 524288000 {product}
openjdk version "1.8.0_162"
最大堆大小為512M或Docker EE安裝設定的2GB的1/4,而不是容器上設定的512M限制。
相比之下,在Java 10上執行相同的命令表明,容器中設定的記憶體限制與預期的128M非常接近:
docker container run -it -m512M --entrypoint bash openjdk:10-jdk
$ docker-java-home/bin/java -XX:+PrintFlagsFinal -version | grep MaxHeapSize
size_t MaxHeapSize = 134217728 {product} {ergonomic}
openjdk version "10" 2018-03-20
設定可用的CPU
預設情況下,每個容器對主機CPU週期的訪問是無限的。可以設定各種約束來限制給定容器對主機CPU週期的訪問。Java 10可以識別這些限制:
docker container run -it --cpus 2 openjdk:10-jdk
jshell> Runtime.getRuntime().availableProcessors()
$1 ==> 2
分配給Docker EE的所有CPU會獲得相同比例的CPU週期。這個比例可以通過修改容器的CPU share權重來調整,而CPU share權重與其它所有執行在容器中的權重相關。此比例僅適用於正在執行的CPU密集型的程序。當某個容器中的任務空閒時,其他容器可以使用餘下的CPU時間。實際的CPU時間的數量取決於系統上執行的容器的數量。這些可以在Java 10中設定:
docker container run -it --cpu-shares 2048 openjdk:10-jdk
jshell> Runtime.getRuntime().availableProcessors()
$1 ==> 2
cpuset約束設定了哪些CPU允許在Java 10中執行。
docker run -it --cpuset-cpus="1,2,3" openjdk:10-jdk
jshell> Runtime.getRuntime().availableProcessors()
$1 ==> 3
分配記憶體和CPU
使用Java 10,可以使用容器設定來估算部署應用程式所需的記憶體和CPU的分配。我們假設已經確定了容器中執行的每個程序的記憶體堆和CPU需求,並設定了JAVA_OPTS配置。例如,如果有一個跨10個節點分佈的應用程式,其中五個節點每個需要512Mb的記憶體和1024個CPU-shares,另外五個節點每個需要256Mb和512個CPU-shares。
請注意,1個CPU share比例由1024表示。
對於記憶體,應用程式至少需要分配5Gb。
512Mb × 5 = 2.56Gb
256Mb × 5 = 1.28Gb
該應用程式需要8個CPU才能高效執行。
1024 x 5 = 5個CPU
512 x 5 = 3個CPU
最佳實踐是建議分析應用程式以確定執行在JVM中的每個程序實際需要多少記憶體和分配多少CPU。但是,Java 10消除了這種猜測,可以通過調整容器大小以防止Java應用程式出現記憶體不足的錯誤以及分配足夠的CPU來處理工作負載。