許多運行在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來處理工作負載。