kubernetes pod memory - java g c logs - PullRequest
       21

kubernetes pod memory - java g c logs

6 голосов
/ 29 апреля 2020

На панели управления kubernetes есть модуль, в котором использование памяти (в байтах) отображается как 904.38Mi.

Этот модуль содержит приложение java, которое было запущено с -Xms512m -Xmx1024m, и на kubernetes файл развертывания -> requests.memory = 512M, limits.memory = 1.5G.

Я включил g c журналов и вижу их в журналах 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.
Любой может пролить свет к этому?

(отредактировано) Когда модуль сначала запускается и запускается в течение нескольких минут, использование памяти отображается только как около 500 МБ, а затем пусть поступающие в него запросы увеличатся до 900 МБ-1 ГБ, затем, когда все было обработано, использование памяти на инструментальной панели k8s не go ниже 900 МБ, хотя, основываясь на журналах G C, куча G C 'ed ok.

Ответы [ 2 ]

3 голосов
/ 30 апреля 2020

Здесь много чего происходит. Давайте рассмотрим каждый из них за раз.

Похоже, вы используете один контейнер на стручок (хотя вы можете иметь много контейнеров на стручок). 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-память падает. Вот как это выглядит на панели инструментов:

enter image description here

Теперь давайте немного изменим этот код. Давайте добавим туда немного 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

при проверке журналов (как в вашем примере), я вижу, что куча собирается просто отлично. Но когда я смотрю на приборную панель, память не падает (не нравится предыдущий пример).

enter image description here

Как только 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

Вот что вы увидите:

heap3

И вам следует, на самом деле, позаботиться об этом :

Это поведение особенно невыгодно в контейнерных средах, где ресурсы оплачиваются путем использования. Даже на этапах, когда виртуальная машина использует только часть назначенных ей ресурсов памяти из-за неактивности, G1 сохранит всю кучу Java. Это приводит к тому, что клиенты все время платят за все ресурсы, а поставщики облачных услуг не могут в полной мере использовать свое оборудование.

PS Я бы также добавил к этому тот факт, что другие стручки тоже страдают, потому что один стручок решил взять столько памяти, сколько мог, при конкретном скачке, и никогда не отдавать ее.

1 голос
/ 29 апреля 2020

G C имеет дело с подмножеством памяти, используемой процессом. Существуют области памяти JVM, которые не подлежат сбору мусора.

Ниже приведены несколько областей памяти, не включенных в кучу / метапространство

  • Пространство стека потоков
  • Сжатое пространство классов
  • JIT-скомпилированный код
  • Прямая буферная память NIO

Приведенный выше список не полон, это просто крупнейшие потребители памяти.

Вот диаграмма иерархии памяти JVM со связанными параметрами конфигурации.

Таким образом, фактический аппетит памяти JVM всегда больше, чем ограничение кучи.

Как большое значение зависит от типа приложения и может быть установлено опытным путем.

ОБНОВЛЕНИЕ

Java Отслеживание собственной памяти может быть включено в JVM для предоставления подробных отчетов, связанных с памятью использование в разных функциональных областях.

...