簡體   English   中英

kubernetes 吊艙 memory - java gc 日志

[英]kubernetes pod memory - java gc logs

在 kubernetes 儀表板上,有一個吊艙,其中 Memory 使用情況(字節)顯示為904.38Mi

This pod holds the java app that was ran with -Xms512m -Xmx1024m , and on kubernetes deployment file -> requests.memory = 512M , limits.memory = 1.5G .

我啟用了 gc 日志並在 pod 日志中看到了這些:

[2020-04-29T15:41:32.051+0000] GC(1533) Phase 1: Mark live objects
[2020-04-29T15:41:32.133+0000] GC(1533) Phase 1: Mark live objects 81.782ms
[2020-04-29T15:41:32.133+0000] GC(1533) Phase 2: Compute new object addresses
[2020-04-29T15:41:32.145+0000] GC(1533) Phase 2: Compute new object addresses 11.235ms
[2020-04-29T15:41:32.145+0000] GC(1533) Phase 3: Adjust pointers
[2020-04-29T15:41:32.199+0000] GC(1533) Phase 3: Adjust pointers 54.559ms
[2020-04-29T15:41:32.199+0000] GC(1533) Phase 4: Move objects
[2020-04-29T15:41:32.222+0000] GC(1533) Phase 4: Move objects 22.406ms
[2020-04-29T15:41:32.222+0000] GC(1533) Pause Full (Allocation Failure) 510M->127M(680M) 171.359ms
[2020-04-29T15:41:32.222+0000] GC(1532) DefNew: 195639K->0K(195840K)
[2020-04-29T15:41:32.222+0000] GC(1532) Tenured: 422769K->130230K(500700K)
[2020-04-29T15:41:32.222+0000] GC(1532) Metaspace: 88938K->88938K(1130496K)
[2020-04-29T15:41:32.228+0000] GC(1532) Pause Young (Allocation Failure) 603M->127M(614M) 259.018ms
[2020-04-29T15:41:32.228+0000] GC(1532) User=0.22s Sys=0.05s Real=0.26s

kubernetes 是如何達到904.38Mi使用率的? 如果我理解正確,目前的用法只有:

DefNew (young) -      0k
Tenured        - 130230K
Metaspace      -  88938K
Sum            - 216168K

運行ps顯示除了這個 java 應用程序之外,pod 上沒有其他進程在運行。
任何人都可以對此有所了解嗎?

(已編輯)當 pod 第一次啟動並運行幾分鍾時,memory 使用量顯示為僅 500mb 左右,然后讓請求進入它會爆發到 900mb-1gb,然后當所有處理完畢后,memory 使用量k8s 儀表板不會 go 低於 900mb,即使基於 GC 日志,堆也可以 GC。

這里發生了很多事情。 讓我們一次拿走每一個。

似乎每個 pod 都使用一個容器(盡管每個 pod 可以有很多容器)。 requests.memorylimits.memory特定於容器,Kubernetes 計算每個podlimitsrequests ,作為所有容器限制的總和。

所以想一想 - 你是說一個pod顯示904.38Mi ,但你顯示requests.memorylimits.memory ,這是每個容器 這就是為什么我假設每個 pod 有一個容器。 這是一個一般介紹,不能回答您的問題 - 但我們會到達那里。

然后是一個pod是由docker啟動的,它以kubectl ,讀取requires.memorylimits.memory 為了簡化一點:您在limits.memory中設置的內容將作為docker -m傳遞。 因此,在您的情況下,用於 docker 進程的總 memory 是1.5GC 請記住,這是整個進程的限制,而不僅僅是堆。 java 進程遠不止堆,您使用-Xms512m -Xmx1024m指定。 所以回答你的問題:

kubernetes 是如何達到 904.38Mi 使用率的?

這是整個過程目前正在采取的,而不僅僅是堆。 從您發布的非常短的日志文件中 - 您的應用程序很好。

編輯

實際上,我的環境中沒有 kubernetes 儀表板來專門測試這個,所以必須安裝它才能真正了解發生了什么。 我對大多數事情都有暗示,但為了確保,我做了一些測試。

首先要做的事情是:儀表板中的數字是什么意思? 花了一段時間才找到/理解,但那是進程的實際駐留 memory ,這實際上是一件非常好的事情。

任何理智的OS都知道,當有人向它請求 memory 時,它很少需要/使用它,因此,它會以一種懶惰的方式將 memory 提供給它。 這在k8s中很容易證明。 假設我有一個jdk-13 JVM 並開始:

kubectl run jdk-13 
    --image=jdk-13 
    --image-pull-policy=Never 
    --limits "memory=100Mi" 
    --requests "memory=10Mi" 
    --command -- /bin/sh -c "while true; do sleep 5; done".

注意requests.memory=10Milimits.memory=100Mi 從頭開始閱讀答案,您已經知道特定的 pod 將以docker -m 100m...啟動,因為limits.memory=100Mi 這很容易證明,只需sh進入pod

 kubectl exec -it jdk-13-b8d656977-rpzrg -- /bin/sh

並找出cgroup說什么:

 # cat /sys/fs/cgroup/memory/memory.limit_in_bytes
 104857600 // 100MB

完美的! 所以 pod 的 memory 限制是最大100 MB ,但是當前的 memory 利用率是多少,即常駐 memory 的利用率是多少?

kubectl top pod
   NAME                          CPU(cores)   MEMORY(bytes)
   jdk-13-b8d656977-rpzrg           1m           4Mi

OK,所以當前的 memory 利用率只有4MB 如果您這樣做,您可以“確保”這確實是准確的:

kubectl exec -it jdk-13-b8d656977-rpzrg -- /bin/sh

並在該吊艙問題內:

top -o %MEM

並注意RES memory 與通過儀表板或kubectl top pod報告的相同。

現在讓我們做一個測試。 假設我在那個 pod 中有這個非常簡單的代碼:

// run this with: java "-Xlog:gc*=debug" -Xmx100m -Xms20m  HeapTest
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class HeapTest {

    public static void main(String[] args) throws Exception {

        // allocate 1 MB every 3 seconds
        for (int i = 0; i < 40; ++i) {
            byte[] b = new byte[1024 * 1024 * 1];
            b[i] = 1;
            System.out.println(Arrays.hashCode(b));
            LockSupport.parkNanos(TimeUnit.of(ChronoUnit.SECONDS).toNanos(3));
        }
    }
}

    

我每 3 秒分配1MB大約 2 分鍾。 當我在儀表板中查看此過程時,我確實看到在某個時間點,memory 會增長。 程序結束后,儀表板報告 memory 的下降。 好的。 這意味着 memory 被歸還並且 RSS memory 下降:這是儀表板中的樣子:

在此處輸入圖像描述

現在讓我們稍微修改一下這段代碼。 讓我們在其中添加一些 GC,讓我們永遠不要完成這個過程(就像典型的 spring-boot 應用程序一樣):

import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class HeapTest {

    public static void main(String[] args) throws Exception {

        // allocate 1 MB every 3 seconds
        for (int i = 0; i < 40; ++i) {
            byte[] b = new byte[1024 * 1024 * 1];
            b[i] = 1;
            System.out.println(Arrays.hashCode(b));
            LockSupport.parkNanos(TimeUnit.of(ChronoUnit.SECONDS).toNanos(3));
        }
        for (int i = 0; i < 10; i++) {
            Thread.sleep(500);
            System.gc();
        }
        
        while (true) {
            try {
                Thread.sleep(TimeUnit.of(ChronoUnit.SECONDS).toMillis(5));
                Thread.onSpinWait();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

    }
}

我運行這個:

java "-Xlog:heap*=debug" 
     "-Xlog:gc*=debug" 
     "-Xlog:ergo*=debug" 
     -Xmx100m 
     -Xms20m
     HeapTest

在檢查日志時(就像在您的示例中一樣),我確實看到堆收集得很好。 但是當我查看儀表板時,memory 並沒有下降(與前面的示例不同)。

在此處輸入圖像描述

一旦G1GC了 memory,它就不太急於把它還給 OS。 在極少數情況下,它可以做到這一點,這是一個示例,或者您可以指示它這樣做

這兩種方式都相當痛苦,相反,有一些更智能的GC算法(並且通常更好)。 我個人的愛去Shenandoah ,讓我們看看它做了什么。 如果我稍微更改代碼(以便我可以更好地證明我的觀點):

import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class HeapTest {

    public static void main(String[] args) throws Exception {

        // allocate 1/4 MB every 100 ms
        for (int i = 0; i < 6000; ++i) {
            byte[] b = new byte[1024 * 256];
            b[i] = 1;
            System.out.println(Arrays.hashCode(b));
            LockSupport.parkNanos(TimeUnit.of(ChronoUnit.MILLIS).toNanos(100));
        }

        while (true) {
            try {
                Thread.sleep(TimeUnit.of(ChronoUnit.SECONDS).toMillis(5));
                Thread.onSpinWait();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

    }
}

並運行它:

 java "-Xlog:gc*=debug" 
      "-Xlog:ergo*=debug" 
      "-Xlog:heap*=debug" 
       -XX:+UnlockExperimentalVMOptions 
       -XX:+UseShenandoahGC 
       -XX:+ShenandoahUncommit 
       -XX:ShenandoahGCHeuristics=compact  
       -Xmx1g 
       -Xms1m  
       HeapTest

以下是您將看到的內容:

堆3

事實上,你應該關心這個

這種行為在資源按使用付費的容器環境中尤其不利。 即使在 VM 由於不活動而僅使用其分配的 memory 資源的一小部分的階段,G1 也會保留所有 Java 堆。 這導致客戶一直為所有資源付費,而雲提供商無法充分利用他們的硬件。

PS我還要補充一點,其他豆莢也正在遭受痛苦,因為一個豆莢決定在特定的峰值處盡可能多地服用memory,並且永遠不會歸還。

GC 處理進程使用的 memory 的子集。 JVM memory 的某些區域不受垃圾回收的影響。

下面是幾個 memory 區域不包含在堆/元空間中

  • 線程堆棧空間
  • 壓縮 Class 空間
  • JIT編譯代碼
  • NIO直接緩沖memory

上面的列表並不完整,這些只是最大的 memory 消費者。

這是帶有相關配置選項的 JVM memory 層次結構圖

總之,實際 JVM memory 的胃口總是大於堆限制。

多大取決於應用的性質,並且可以根據經驗確定。

更新

Java Native Memory Tracking could be enabled in JVM to provide detailed reports related to memory usage across different functional areas.

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM