了解 Docker 中的 Java 記憶體行為
我們目前在 Google Container Engine (GKE) 中執行的 Kubernetes 集群由
n1-standard-1
執行由 Google 維護的 Debian GNU/Linux 7.9 (wheezy) 的機器類型(1 個虛擬 CPU 和 3.75 GB 記憶體)組成。由於我們服務的負載和記憶體使用量增加,我們需要將節點升級到更大的機器類型。在我們的測試集群中嘗試這個時,我們經歷了一些(對我們來說)似乎很奇怪的事情。JVM 應用程序在部署到 Google 節點時消耗的記憶體似乎與節點上可用的核心數成正比。即使我們將 JVM 最大記憶體 (Xmx) 設置為 128Mb,它在 1 核機器上也消耗大約 250Mb(這是可以理解的,因為 JVM 由於 GC、JVM 本身等原因消耗的記憶體超過了最大限制),但它消耗大約2 核機器 (
n1-standard-2
) 上 700Mb 和 4 核機器 ( ) 上大約 1.4Gbn1-standard-4
。唯一不同的是機器類型,使用相同的 Docker 鏡像和配置。例如,如果我使用
n1-standard-4
機器類型通過 SSH 連接到機器並執行,sudo docker stats <container_name>
我會得到以下資訊:CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O k8s.name 3.84% 1.11 GB / 1.611 GB 68.91% 0 B / 0 B
當我在本地使用完全相同的(應用程序)配置(mac osx 和 docker-machine)執行相同的Docker映像時,我看到:
CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O name 1.49% 236.6 MB / 1.044 GB 22.66% 25.86 kB / 14.84 kB 0 B / 0 B
這更符合我對
Xmx
設置的期望(據記錄,我有 8 個核心和 16Gb 的記憶體)。當我top -p <pid>
在 GKE 實例上執行時,同樣的事情得到了證實,這給了我 1.1 到 1.4 Gb 的 RES/RSS 記憶體分配。Docker 鏡像定義如下:
FROM java:8u91-jre EXPOSE 8080 EXPOSE 8081 ENV JAVA_TOOL_OPTIONS -Dfile.encoding=UTF-8 # Add jar ADD uberjar.jar /data/uberjar.jar CMD java -jar /data/uberjar.jar -Xmx128m -server
我也嘗試添加:
ENV MALLOC_ARENA_MAX 4
我已經在諸如此類的幾個執行緒中看到了推薦,但它似乎並沒有任何區別。我也嘗試過更改為另一個 Java 基礎映像以及使用 alpine linux,但這似乎也沒有改變。
我本地的 Docker 版本是 1.11.1,Kubernetes/GKE 中的 Docker 版本是 1.9.1。Kubernetes 版本(如果重要)是 v1.2.4。
同樣有趣的是,如果我們將 pod/容器的多個實例部署到同一台機器/節點,一些實例將分配更少的記憶體。例如,前三個可能會分配 1.1-1.4Gb 的記憶體,但隨後的 10 個容器僅分配大約 250 Mb,這大約是我期望的每個要分配的實例。問題是,如果我們達到機器的記憶體限制,前三個實例(分配 1.1Gb 記憶體的實例)似乎永遠不會釋放它們分配的記憶體。如果他們在機器承受更大的壓力時釋放記憶體,我不會擔心這一點,但因為即使在機器載入時它們也會保留記憶體分配,這會成為一個問題(因為它禁止在這台機器上調度其他容器並且因此資源被浪費)。
問題:
- 什麼可能導致這種行為?這是JVM問題嗎?碼頭工人問題?虛擬機問題?Linux 問題?配置問題?或者也許是一個組合?
- 在這種情況下,我可以嘗試做些什麼來限制 JVM 的記憶體分配?
問題出在
Dockerfile
. 我們像這樣啟動我們的 JVM:CMD java -jar /data/uberjar.jar -Xmx128m -server
但是當我們切換到:
CMD java -Xmx128m -server -jar /data/uberjar.jar
這些設置被考慮在內。我仍然不明白為什麼我沒有在本地看到這個,但我很高興我們設法解決了它。
當您指定
CMD java -jar /data/uberjar.jar -Xmx128m -server
然後您打算作為 JVM 參數 (
-Xmx128m -server
) 的值作為命令行參數傳遞給 .jar 文件中的 Main 類。它們可作為您public static void main(String... args)
方法中的參數使用。請注意,如果您按名稱執行主類,而不是指定執行檔,這也是正確的
jar
。要將它們作為 JVM 參數而不是程序的參數傳遞,您需要在
-jar
arg 之前指定它們。