首頁>科技>

在AWS雲上,我們執行並部署容器化應用程式到我們的PaaS管道。像我們這樣在Docker中執行Java應用程式的人,可能已經遇到過 JVM在容器中執行時無法準確檢測可用記憶體的問題 。jvm沒有準確地檢測Docker容器中可用的記憶體,而是檢視機器的可用記憶體。這可能導致在容器內執行的應用程式在嘗試使用超出Docker容器限制的記憶體量時被終止的情況。

JVM對可用記憶體的錯誤檢測與Linux tools/lib 有關,這些 tools/lib 是在 cgroups 存在之前建立的,用於返回系統資源資訊(例如, /proc/meminfo , /proc/vmstat )。它們返回主機的資源資訊(該主機是物理機還是虛擬機器)。

讓我們透過觀察一個簡單的Java應用程式在Docker容器中執行時如何分配一定百分比的記憶體來探索這個過程。我們將把應用程式部署為Kubernetes pod(使用Minikube)來說明Kubernetes上也存在這個問題,這並不奇怪,因為Kubernetes使用Docker作為容器引擎。

import java.util.Vector;public class MemoryConsumer {    private static float CAP = 0.8f;  // 80%    private static int ONE_MB = 1024 * 1024;    private static Vector cache = new Vector();    public static void main(String[] args) {        Runtime rt = Runtime.getRuntime();        long maxMemBytes = rt.maxMemory();        long usedMemBytes = rt.totalMemory() - rt.freeMemory();        long freeMemBytes = rt.maxMemory() - usedMemBytes;        int allocBytes = Math.round(freeMemBytes * CAP);        System.out.println("Initial free memory: " + freeMemBytes/ONE_MB + "MB");        System.out.println("Max memory: " + maxMemBytes/ONE_MB + "MB");        System.out.println("Reserve: " + allocBytes/ONE_MB + "MB");        for (int i = 0; i < allocBytes / ONE_MB; i++){            cache.add(new byte[ONE_MB]);        }        usedMemBytes = rt.totalMemory() - rt.freeMemory();        freeMemBytes = rt.maxMemory() - usedMemBytes;        System.out.println("Free memory: " + freeMemBytes/ONE_MB + "MB");    }}

我們使用Docker構建檔案來建立包含 jar 的影象, jar 是從上面的Java程式碼構建的。我們需要這個Docker映像,以便將應用程式部署為Kubernetes pod。

Dockerfile

FROM openjdk:8-alpineADD memory_consumer.jar /opt/local/jars/memory_consumer.jarCMD java $JVM_OPTS -cp /opt/local/jars/memory_consumer.jar com.banzaicloud.MemoryConsumer
docker build -t memory_consumer .

現在我們有了Docker映像,我們需要建立一個pod定義來將應用程式部署到kubernetes:

memory-consumer.yaml
apiVersion: v1kind: Podmetadata:  name: memory-consumerspec:  containers:  - name: memory-consumer-container    image: memory_consumer    imagePullPolicy: Never    resources:      requests:        memory: "64Mi"      limits:        memory: "256Mi"  restartPolicy: Never

此pod定義確保將容器排程到至少有64MB可用記憶體的節點,並且不允許其使用超過256MB的記憶體。

$ kubectl create -f memory-consumer.yamlpod "memory-consumer" created

pod輸出:

$ kubectl logs memory-consumerInitial free memory: 877MBMax memory: 878MBReserve: 702MBKilled$ kubectl get po --show-allNAME              READY     STATUS      RESTARTS   AGEmemory-consumer   0/1       OOMKilled   0          1m

在容器內執行的Java應用程式檢測到877MB的可用記憶體,因此試圖保留702MB的可用記憶體。因為我們之前將最大記憶體使用限制為256MB,所以容器被終止。

為了避免這種結果,我們需要指示JVM可以保留的最大記憶體量。我們透過 -Xmx 選項來實現。我們需要修改pod定義,將 -Xmx 設定透過 JVM_OPTS 環境變數傳遞給容器中的Java應用程式。

memory-consumer.yaml

apiVersion: v1kind: Podmetadata:  name: memory-consumerspec:  containers:  - name: memory-consumer-container    image: memory_consumer    imagePullPolicy: Never    resources:      requests:        memory: "64Mi"      limits:        memory: "256Mi"    env:    - name: JVM_OPTS      value: "-Xms64M -Xmx256M"  restartPolicy: Never
$ kubectl delete pod memory-consumerpod "memory-consumer" deleted$ kubectl get po --show-allNo resources found.$ kubectl create -f memory_consumer.yamlpod "memory-consumer" created$ kubectl logs memory-consumerInitial free memory: 227MBMax memory: 228MBReserve: 181MBFree memory: 50MB$ kubectl get po --show-allNAME              READY     STATUS      RESTARTS   AGEmemory-consumer   0/1       Completed   0          1m

這次應用程式執行成功;它檢測到我們透過 -Xmx256M 傳遞的正確可用記憶體,因此沒有達到pod定義中指定的記憶體限制記憶體“ 256Mi ”。

雖然這個解決方案可行,但它需要在兩個地方指定記憶體限制:一個是作為容器記憶體的限制:“256Mi”,另一個是在傳遞給 -Xmx256M 的選項中。如果JVM根據記憶體“256Mi”設定準確地檢測到可用記憶體的最大數量,會更方便,不是嗎?

好吧,Java9中有一個變化,使它具有Docker意識,它已經被後移植到Java8。

為了利用此功能,我們的pod定義必須如下所示:

memory-consumer.yaml
apiVersion: v1kind: Podmetadata:  name: memory-consumerspec:  containers:  - name: memory-consumer-container    image: memory_consumer    imagePullPolicy: Never    resources:      requests:        memory: "64Mi"      limits:        memory: "256Mi"    env:    - name: JVM_OPTS      value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -Xms64M"  restartPolicy: Never
$ kubectl delete pod memory-consumerpod "memory-consumer" deleted$ kubectl get pod --show-allNo resources found.$ kubectl create -f memory_consumer.yamlpod "memory-consumer" created$ kubectl logs memory-consumerInitial free memory: 227MBMax memory: 228MBReserve: 181MBFree memory: 54MB$ kubectl get po --show-allNAME              READY     STATUS      RESTARTS   AGEmemory-consumer   0/1       Completed   0          50s

請注意 -XX:MaxRAMFraction=1 ,透過它我們可以告訴JVM要使用多少可用記憶體作為最大堆大小。

透過 -Xmx 或 UseCGroupMemoryLimitForHeap 動態設定一個考慮可用記憶體限制的最大堆大小是很重要的,因為它有助於在記憶體使用接近其限制時通知JVM,以便釋放空間。如果最大堆大小不正確(超過可用記憶體限制),JVM可能會盲目地達到該限制而不嘗試釋放記憶體,程序將被 OOMKilled

這個 java.lang.OutOfMemoryError 錯誤是不同的。它表示最大堆大小不足以在記憶體中容納所有活動物件。如果是這種情況,則需要透過 -Xmx 增加最大堆大小,或者如果使用 UseCGroupMemoryLimitForHeap ,則透過容器的記憶體限制增加最大堆大小。

9
最新評論
  • 整治雙十一購物亂象,國家再次出手!該跟這些套路說再見了
  • 世界晶片供應嚴重短缺,源頭到底是哪裡?日媒調查後道出關鍵