天天看點

java jar killed_我的Java應用程式被OOMKilled了,原因竟是這樣?

java jar killed_我的Java應用程式被OOMKilled了,原因竟是這樣?

在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作為容器引擎。

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-alpine

ADD memory_consumer.jar /opt/local/jars/memory_consumer.jar

CMD 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: v1

kind: Pod

metadata:

name: memory-consumer

spec:

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.yaml

pod "memory-consumer" created

pod輸出:

$ kubectl logs memory-consumer

Initial free memory: 877MB

Max memory: 878MB

Reserve: 702MB

Killed

$ kubectl get po --show-all

NAME READY STATUS RESTARTS AGE

memory-consumer 0/1 OOMKilled 0 1m

在容器内運作的Java應用程式檢測到877MB的可用記憶體,是以試圖保留702MB的可用記憶體。因為我們之前将最大記憶體使用限制為256MB,是以容器被終止。

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

memory-consumer.yaml

apiVersion: v1

kind: Pod

metadata:

name: memory-consumer

spec:

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-consumer

pod "memory-consumer" deleted

$ kubectl get po --show-all

No resources found.

$ kubectl create -f memory_consumer.yaml

pod "memory-consumer" created

$ kubectl logs memory-consumer

Initial free memory: 227MB

Max memory: 228MB

Reserve: 181MB

Free memory: 50MB

$ kubectl get po --show-all

NAME READY STATUS RESTARTS AGE

memory-consumer 0/1 Completed 0 1m

這次應用程式運作成功;它檢測到我們通過 -Xmx256M 傳遞的正确可用記憶體,是以沒有達到pod定義中指定的記憶體限制記憶體“ 256Mi ”。

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

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

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

memory-consumer.yaml

apiVersion: v1

kind: Pod

metadata:

name: memory-consumer

spec:

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-consumer

pod "memory-consumer" deleted

$ kubectl get pod --show-all

No resources found.

$ kubectl create -f memory_consumer.yaml

pod "memory-consumer" created

$ kubectl logs memory-consumer

Initial free memory: 227MB

Max memory: 228MB

Reserve: 181MB

Free memory: 54MB

$ kubectl get po --show-all

NAME READY STATUS RESTARTS AGE

memory-consumer 0/1 Completed 0 50s

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

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

這個 java.lang.[OutOfMemoryError](http://javakk.com/tag/outofmemoryerror "檢視更多關于 OutOfMemoryError 的文章") 錯誤是不同的。它表示最大堆大小不足以在記憶體中容納所有活動對象。如果是這種情況,則需要通過 -Xmx 增加最大堆大小,或者如果使用 UseCGroupMemoryLimitForHeap ,則通過容器的記憶體限制增加最大堆大小。