Здесь много чего происходит. Давайте рассмотрим каждый из них за раз.
Похоже, вы используете один контейнер на стручок (хотя вы можете иметь много контейнеров на стручок). requests.memory
и limits.memory
указаны c для контейнера , kubernets вычисляет limits
и requests
на pod как сумму всех ограничений контейнеров.
Так что подумайте - вы говорите, что pod
показывает 904.38Mi
, но вы показываете requests.memory
и limits.memory
, что соответствует контейнеру . Вот почему я предполагаю, что у вас есть один контейнер на стручок. Это общее введение, которое не отвечает на ваш вопрос - но мы доберемся до него.
Затем наступает тот факт, что pod
запускается docker
, который начинается с kubectl
, и в нем говорится: requires.memory
и limits.memory
. Чтобы сделать это немного проще: то, что вы установили в limits.memory
, будет передано как docker -m
. Итак, в вашем случае общая память, используемая для процесса docker, составляет 1.5GC
. Помните, что это весь предел процесса, а не только куча. Процесс java намного больше, чем куча, которую вы указываете с -Xms512m -Xmx1024m
. Итак, чтобы ответить на ваш вопрос:
Как kubernetes пришел на использование 904.38Mi?
Это то, что в настоящее время принимает процесс весь , а не просто куча. Из очень коротких файлов журнала, которые вы разместили - ваше приложение просто отлично.
РЕДАКТИРОВАТЬ
У меня фактически не было панели управления kubernetes в моей среде, чтобы протестировать это специально, поэтому пришлось установить ее, чтобы действительно понять, что происходит. У меня был намек на большинство вещей, но чтобы убедиться, я провёл несколько тестов.
Перво-наперво: что означает это число на приборной панели? Потребовалось время, чтобы найти / понять, но это фактическая резидентная память процесса , что на самом деле очень хорошая вещь.
Любой здравомыслящий 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=10Mi
и limits.memory=100Mi
. Читая ответ с самого начала, вы уже знаете, что указанный модуль c будет запущен с 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
идеально! таким образом, ограничение памяти модуля составляет 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
и внутри этой проблемы с модулем:
top -o %MEM
и заметите, что RES
память находится на одном уровне с сообщенным через приборную панель или kubectl top 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-память падает. Вот как это выглядит на панели инструментов:
Теперь давайте немного изменим этот код. Давайте добавим туда немного G C и никогда не закончим sh этот процесс (вы знаете, как это делают типичные приложения с весенней загрузкой):
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
при проверке журналов (как в вашем примере), я вижу, что куча собирается просто отлично. Но когда я смотрю на приборную панель, память не падает (не нравится предыдущий пример).
Как только 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 {
// 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
Вот что вы увидите:
И вам следует, на самом деле, позаботиться об этом :
Это поведение особенно невыгодно в контейнерных средах, где ресурсы оплачиваются путем использования. Даже на этапах, когда виртуальная машина использует только часть назначенных ей ресурсов памяти из-за неактивности, G1 сохранит всю кучу Java. Это приводит к тому, что клиенты все время платят за все ресурсы, а поставщики облачных услуг не могут в полной мере использовать свое оборудование.
PS Я бы также добавил к этому тот факт, что другие стручки тоже страдают, потому что один стручок решил взять столько памяти, сколько мог, при конкретном скачке, и никогда не отдавать ее.