Назад | Перейти на главную страницу

Понимание поведения памяти Java в Docker

Наш текущий кластер Kubernetes, работающий в Google Container Engine (GKE), состоит из n1-standard-1 типы машин (1 виртуальный процессор и 3,75 ГБ памяти) под управлением Debian GNU / Linux 7.9 (wheezy), поддерживаемого Google. Из-за увеличения нагрузки и использования памяти нашими службами нам необходимо обновить наши узлы до более крупного типа. Пробуя это в нашем тестовом кластере, мы испытали кое-что, что (нам) кажется довольно странным.

Объем памяти, потребляемой приложением JVM при развертывании на узле Google, кажется, пропорционален количеству ядер, доступных на узле. Даже если мы установим максимальную память JVM (Xmx) на 128 МБ, она потребляет около 250 МБ на одноядерной машине (это понятно, поскольку JVM потребляет больше памяти, чем максимальный предел из-за GC, самой JVM и т. Д.), Но она потребляет около 700Мб на 2-х ядерной машине (n1-standard-2) и около 1,4 ГБ на 4-ядерной машине (n1-standard-4). Единственное, что отличается - это тип машины, используется тот же образ и конфигурации Docker.

Например, если я подключусь к машине по SSH, используя n1-standard-4 тип машины и запуск sudo docker stats <container_name> Я получаю это:

CONTAINER CPU %               MEM USAGE / LIMIT    MEM %               NET I/O             BLOCK I/O
k8s.name  3.84%               1.11 GB / 1.611 GB   68.91%              0 B / 0 B 

Когда я запускаю тем же Образ Docker с точным тем же (приложение) конфигурация локально (mac osx и докер-машина) я вижу:

CONTAINER CPU %               MEM USAGE / LIMIT    MEM %               NET I/O               BLOCK I/O
name      1.49%               236.6 MB / 1.044 GB  22.66%              25.86 kB / 14.84 kB   0 B / 0 B         

Что гораздо больше соответствует тому, что я ожидал из-за Xmx Установка (для записи у меня 8 ядер и 16Гб памяти). То же самое подтверждается, когда я бегу top -p <pid> на экземпляре GKE, который дает мне выделение памяти RES / RSS от 1,1 до 1,4 ГБ.

Образ Docker определяется так:

FROM java:8u91-jre
EXPOSE 8080
EXPOSE 8081

ENV JAVA_TOOL_OPTIONS -Dfile.encoding=UTF-8

# Add jar
ADD uberjar.jar /data/uberjar.jar

CMD java -jar /data/uberjar.jar -Xmx128m -server

Я также пробовал добавить:

ENV MALLOC_ARENA_MAX 4

который я видел рекомендованным в нескольких потоках, таких как этот но похоже, что это не имеет никакого значения. Я также пытался перейти на другой базовый образ Java, а также использовать alpine linux, но это, похоже, тоже ничего не меняет.

Моя локальная версия Docker - 1.11.1, а версия Docker в Kubernetes / GKE - 1.9.1. Версия Kubernetes (если это важно) - v1.2.4.

Также интересно то, что если мы развернем несколько экземпляров модуля / контейнера на одном компьютере / узле, некоторые экземпляры будут выделять гораздо меньше памяти. Например, первые три могут выделять 1,1–1,4 ГБ памяти, но затем 10 последующих контейнеров выделяют только около 250 МБ каждый, что примерно соответствует тому, что я ожидал. каждый экземпляр выделить. Проблема в том, что если мы достигаем ограничения памяти машины, первые три экземпляра (те, которые выделяют 1,1 ГБ памяти), похоже, никогда не освобождают свою выделенную память. Если бы они освободили память, когда машина находится под повышенным давлением, я бы не стал беспокоиться об этом, но поскольку они сохраняют выделение памяти даже при загрузке машины, это становится проблемой (поскольку это запрещает планирование других контейнеров на этой машине и таким образом ресурсы тратятся зря).

Вопросы:

  1. Что могло быть причиной такого поведения? Это проблема JVM? Проблема с докером? Проблема с виртуальной машиной? Проблема с Linux? Проблема с конфигурацией? А может комбинация?
  2. Что я могу сделать, чтобы ограничить выделение памяти JVM в этом случае?

Когда вы указываете

CMD java -jar /data/uberjar.jar -Xmx128m -server

затем значения, которые вы хотите использовать в качестве аргументов JVM (-Xmx128m -server) передаются как аргументы командной строки классу Main в файле .jar. Они доступны в виде аргументов в вашем public static void main(String... args) метод.

Обратите внимание, что это также верно, если вы запускаете основной класс по имени, а не указываете исполняемый файл jar файл.

Чтобы передать их в качестве аргументов JVM, а не аргументов в вашу программу, вам необходимо указать их перед -jar арг.

Видеть https://stackoverflow.com/questions/5536476/passing-arguments-to-jar-which-is-required-by-java-interpreter

Проблема заключалась в настройках JVM, указанных в Dockerfile. Мы запустили нашу JVM так:

CMD java -jar /data/uberjar.jar -Xmx128m -server

но когда мы перешли на:

CMD java -Xmx128m -server -jar /data/uberjar.jar

настройки учтены. Я до сих пор не понимаю, почему я не видел это локально, но я просто рад, что нам удалось это решить.