这里有很多事情要处理,我们逐一来看。似乎您每个pod使用单个容器(尽管您可以每个pod使用许多容器)。requests.memory和limits.memory是特定于容器的,Kubernetes将所有容器的限制之和作为pod的limits和requests。
因此,请考虑一下-您正在说pod显示904.38Mi,但您显示的是requests.memory和limits.memory,这是每个容器的。这就是我认为您每个pod使用一个容器的原因。这是一个一般性介绍,没有回答您的问题,但我们会解决这个问题。
然后是一个pod由docker启动的事实,docker由kubectl启动,并读取requires.memory和limits.memory。为了使这更简单:在limits.memory中设置的内容将作为docker-m传递。因此,在您的情况下,用于docker进程的总内存为1.5GC。请记住,这是整个进程的限制,而不仅仅是堆。Java进程不仅仅是堆,您可以使用-Xms512m-Xmx1024m进行指定。所以回答你的问题:
Kubernetes如何得出904.38Mi的使用情况?
这是整个进程当前正在占用的,而不仅仅是堆。从您发布的非常短的日志文件中可以看出,您的应用程序运行良好。
编辑
实际上,我的环境中没有kubernetes仪表板来专门测试这一点,因此必须安装它才能真正了解发生了什么。我有一个提示针对大多数事情,但为了确保,我进行了一些测试。
首先要做的是:仪表板中的那个数字是什么意思?花了一段时间才找到/理解,但那是
进程实际驻留内存,这实际上是一件非常好的事情。
任何明智的操作系统都知道,当有人从中请求内存时,它很少需要/利用全部内存,因此,它以懒惰的方式将内存分配给它。这在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=10Mi
和 limits.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的内存限制最大为100 MB
,但是当前的内存利用率是多少?也就是说,占用了多少驻留内存?
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中有这个非常简单的代码:
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 {
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分钟。当我在仪表盘中查看此进程时,我确实看到某个时间点内存增长。程序结束后,仪表板报告内存下降。好的!这意味着内存已经归还,RSS内存下降。在仪表盘中的显示如下:
![enter image description here](https://istack.dev59.com/d3w97.webp)
现在让我们稍微改变一下这段代码。让我们添加一些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 {
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](https://istack.dev59.com/KzoSa.webp)
一旦
G1GC
占用内存,它就不太愿意将其归还给操作系统。虽然有时候会发生这种情况,例如
一个例子 或者
您可以指示它这样做,但两种方法都相当痛苦。相反,有些
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 {
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](https://istack.dev59.com/rXLQO.webp)
而且你确实应该关心这个问题:
在容器环境中,这种行为特别不利,因为资源是按使用付费的。即使在虚拟机由于闲置而只使用其分配的内存资源的一小部分的阶段,G1也会保留所有Java堆。这导致客户始终支付所有资源的费用,云提供商无法充分利用其硬件。
P.S. 我还要补充一点,就是其他Pod也会受到影响,因为一个Pod决定尽可能多地占用内存,而在特定的峰值时从未归还。
free -m
命令?它可以告诉你容器实际使用了多少内存,并可能帮助你发现Java进程不知道的泄漏问题。 - Yaron Idanfree -m
是无用的。只需尝试在容器内运行docker run -m...
,然后执行free -m
即可。 - Eugene