java - kubernetes pod 内存 - java gc 日志

标签 java kubernetes memory-management garbage-collection kubernetes-metrics

在 kubernetes 仪表板上,有一个 pod,其中内存使用情况(字节)显示为 904.38Mi .

这个 pod 包含使用 -Xms512m -Xmx1024m 运行的 Java 应用程序, 以及 kubernetes 部署文件 -> 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第一次启动并运行几分钟时,内存使用情况仅显示为500mb左右,然后让请求进来它会突然增加到900mb-1gb,然后当所有处理完毕后,k8s仪表板上的内存使用情况不会低于 900mb,即使基于 GC 日志,堆也可以通过 GC。

最佳答案

这里发生了很多事情。让我们一次一个。
您似乎每个 pod 使用一个容器(尽管每个 pod 可以有多个容器)。 requests.memorylimits.memory特定于容器,Kubernetes 计算 limitsrequests每个 pod 作为所有容器限制的总和。
所以想一想 - 你说的是 pod显示 904.38Mi ,但你显示 requests.memorylimits.memory ,这是每个容器。这就是为什么我假设每个 pod 有一个容器。这是一般性介绍,不能回答您的问题 - 但我们会到达那里。
然后是一个 pod 的事实由 docker 发起,以 kubectl 开头上面写着 requires.memorylimits.memory .为了使这更简单:您在 limits.memory 中设置的内容, 将被传递为 docker -m .因此,在您的情况下,用于 docker 进程的总内存为 1.5GC .请记住,这是整个进程的限制,而不仅仅是堆。一个 java 进程远不止是堆,你用 -Xms512m -Xmx1024m 指定了堆。 .所以要回答你的问题:

How did kubernetes arrived at 904.38Mi usage?


这就是整个过程当前正在执行的操作,而不仅仅是堆。从您发布的非常短的日志文件来看 - 您的应用程序很好。
编辑
实际上,我的环境中没有 kubernetes 仪表板来专门对此进行测试,因此必须安装它才能真正了解正在发生的事情。我对大多数事情都有一些暗示,但为了确定,我做了一些测试。
第一件事:仪表板中的数字是什么意思?花了一段时间才找到/理解,但那是 the actual resident memory of the process ,这实际上是一件非常好的事情。
任何理智 OS知道当有人向它请求内存时,它很少需要/使用它,因此,它以一种懒惰的方式将内存提供给它。这在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 .一开始看答案,你已经知 Prop 体的pod会以docker -m 100m...开头因为 limits.memory=100Mi .这很容易证明,只是shpod :
 kubectl exec -it jdk-13-b8d656977-rpzrg -- /bin/sh
并找出 cgroup说:
 # cat /sys/fs/cgroup/memory/memory.limit_in_bytes
 104857600 // 100MB
完美的!所以 pod 的内存限制是 100 MB max,但是当前的内存利用率是多少,也就是占用的常驻内存是多少?
kubectl top pod
   NAME                          CPU(cores)   MEMORY(bytes)
   jdk-13-b8d656977-rpzrg           1m           4Mi
好的,所以当前的内存利用率只有 4MB .
如果您这样做,您可以“确保”这确实是准确的:
kubectl exec -it jdk-13-b8d656977-rpzrg -- /bin/sh
在那个 pod 问题中:
top -o %MEM
并注意 RES内存与通过仪表板或 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));
        }
    }
}

    
我分配1MB每 3 秒一次,持续约 2 分钟。当我在仪表板中查看这个过程时,我确实看到在某个时间点,内存在增长。程序结束后,仪表板会报告内存下降。好的!这意味着内存被收回,RSS 内存下降。这是仪表板中的样子:
enter image description here
现在让我们稍微改变一下这段代码。让我们在那里添加一些 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
在检查日志时(就像在您的示例中一样),我确实看到堆收集得很好。但是当我查看仪表板时,内存并没有下降(与前面的示例不同)。
enter image description here
曾经G1GC占用内存,它不是很想把它还给操作系统。它可以在极少数情况下做到这一点,这里是 one exampleyou can instruct it to do so .
两种方式都比较痛苦,取而代之的是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
以下是您将看到的内容:
heap3
还有你should, for a fact care about this :

This behavior is particularly disadvantageous in container environments where resources are paid by use. Even during phases where the VM only uses a fraction of its assigned memory resources due to inactivity, G1 will retain all of the Java heap. This results in customers paying for all resources all the time, and cloud providers not being able to fully utilize their hardware.


附言我还要补充一点,其他 pod 也受到影响,因为一个 pod 决定在特定峰值时尽可能多地占用内存,并且永不归还。

关于java - kubernetes pod 内存 - java gc 日志,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61506136/

相关文章:

日志类的 JAVA_HOME 问题

spring - 说我通过 Kubernetes 部署组件是否正确?

django - 如何使用django-health-check实现生存和准备状态

c++ - memcpy 可以重新分配缓冲区吗?

c++ - 堆栈和堆上的 STL 容器

c - 如何释放使用 malloc 分配的内存?

java - 从生成的源创建 XML

java - 使用 GWT 重复发布帖子

java - Gson 不序列化子类中定义的字段

ubuntu - microk8s,DEVOPS : Unable to connect to the server: x509: certificate is valid for <internal IPs>, 不是 <外部 IP>