Об одной неприятной проблеме сборщика Concurrent Mark-Sweep,...

48
1

description

В Java важно правильно настраивать сборку мусора (Garbage Collection), поскольку она может оказывать огромное влияние на производительность Java-приложения. Однако обилие алгоритмов и настроек GC может сбить с толку. В своём докладе я расскажу о проблеме долгих и частых остановок Java-приложения, которые сборщик Concurrent Mark-Sweep вызывает при неправильных настройках, а также о том, как эту проблему удалось устранить.

Transcript of Об одной неприятной проблеме сборщика Concurrent Mark-Sweep,...

Page 1: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

1

Page 2: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

2

Об одной неприятной

проблеме сборщика

Concurrent Mark-Sweep

Page 3: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

3

Обо мне

• Java-разработчик сервисов

Яндекс.Метрика (большой метрики,

метрики приложений, внутренних

сервисов метрики)

• Увлекаюсь математикой и прикладными

науками, связанными с ней: машинное

обучение, информационный поиск,

криптография

Page 4: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

4

Содержание

• Постановка задачи

• Какие сборщики мусора мы бы могли выбрать

• Почему мы выбрали Concurrent Mark-Sweep и

как он работает

• Как мы тюнили сборки в новом и старом

поколениях

• Что ещё можно было бы придумать

Page 5: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

5

Постановка задачи

• Есть N машин, на которых развёрнуто Java-приложение

• Приложение имеет внутренний сервис (джоб) – сотни тредов,

high concurrency, большие объёмы обрабатываемых данных

• В каждый момент времени должно работать не более одного

джоба, поэтому N машин соединены координатором Apache

Curator Framework (АПИ над Apache ZooKeeper)

• Проблема: координатор очень часто переключает выполнение

джоба с машины на машину, джоб едва ли успевает полностью

отработать на какой-то из машин

Page 6: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

6

Куда и что смотреть?

• Смотреть в логи машины, с которой произошло

переключение

• А там что-то типа этого:

2014.01.24 17:44:37 [DNS Resolver reader] WARN

..monitoringd.process.MonitoringSimple - addEntry tried to lock checkStateLock, but

failed

2014.01.24 17:46:02 [spring-scheduler-3-SendThread(mtmon2.yandex.ru:2181)] INFO

org.apache.zookeeper.ClientCnxn - Client session timed out, have not heard from

server in 87741ms for sessionid 0x347200ebf2ac586, closing socket connection and

attempting reconnect

• И такое встречается в логе каждые 10 минут…

Page 7: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

7

Какой у нас Java Heap Space?

• Находим наш процесс через что-то типа

ps –ef | grep java

jps -v

и смотрим на параметры -Xms и -Xmx

• Если этих параметров нет, то используем:

jconsole, или

kill -3 <pid>

• У нас хип на 14 гб: -Xms1G -Xmx14G

Page 8: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

8

Java Heap Space

Page 9: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

9

Какие у нас алгоритмы GC?

• Настройки GC:

-XX:+UseParNewGC

-XX:+UseConcMarkSweepGC

Page 10: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

10

Расширенные настройки

• -Xloggc:gc.log

• -XX:+PrintGCDetails

• -XX:+PrintGCDateStamps

• -XX:-PrintGCTimeStamps

Page 11: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

11

PrintGCDateStamps

• Почему надо -XX:+PrintGCDateStamps:

2014-01-26T20:00:53.282+0400: [GC [ParNew: 307377K->479K(345024K),

0.0956740 secs] 6653986K->6609342K(12544576K) icms_dc=72 , 0.0958890

secs] [Times: user=1.51 sys=0.01, real=0.09 secs]

Page 12: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

12

PrintGCTimeStamps

• Почему надо -XX:-PrintGCTimeStamps:

2014-07-20T09:54:31.478-0700: 208.268: [GC (Allocation Failure)

Page 13: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

13

concurrent mode failure

• 2014-01-28T08:30:09.350+0400: [GC [ParNew (promotion failed): 471872K-

>471872K(471872K), 1.8524430 secs][CMS2014-01-28T08:30:17.973+0400:

[CMS-concurrent-sweep: 11.525/15.409 secs] [Times: user=40.39 sys=0.29,

real=15.41 secs]

• (concurrent mode failure): 10825312K->6356437K(12058624K), 47.1781340

secs] 11092737K->6356437K(12530496K), [CMS Perm : 44494K-

>44405K(74252K)], 49.0308390 secs] [Times: user=62.06 sys=0.09,

real=49.03 secs]

Page 14: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

14

Что такое concurrent mode failure

• CMS (в основном) работает параллельно с тредами приложения

• Из-за этого может быть 2 ситуации:

- Tenured generation наполняется быстрее, чем CMS освобождает

там память. В итоге, рано или поздно, из young generation надо

перенести больший объём (в байтах), чем свободно в tenured

generation

- В tenured generation есть необходимое свободное место, но оно

сильно фрагментировано

• Обе ситуации генерируют особое исключение CMS – concurrent

mode failure. По сути, это однопоточный(!!!) Full GC.

Page 15: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

15

Почему бы не использовать другие

сборщики? • Serial Collector

• Parallel Collector

• Garbage-First (G1) Garbage Collector

Page 16: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

16

Serial collector

• STW collector… и слишком долго работает…

Page 17: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

17

G1 GC

• Неплохая замена CMS, но полная поддержка

появилась только в Oracle HotSpot 7 update 4

Page 18: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

18

Parallel collector

• STW collector

• Есть редкие ошибки в его работе:

4071460.500: [Full GC#

# A fatal error has been detected by the Java Runtime Environment:

#

# SIGSEGV (0xb) at pc=0x00007fdbfec78177, pid=20405, tid=140581109962496

#

# JRE version: 6.0_45-b06

# Java VM: Java HotSpot(TM) 64-Bit Server VM (20.45-b01 mixed mode linux-amd64 compressed oops)

# Problematic frame:

# V [libjvm.so+0x759177] PSMarkSweepDecorator::precompact()+0x307

#

# An error report file with more information is saved as:

# /tmp/hs_err_pid20405.log

Page 19: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

19

Как работает CMS

- Initial mark – STW

- Mark

- Preclean

- Remark – STW

- Sweep

- Reset

Page 20: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

20

CMS: Initial Mark

• CMS останавливает все треды и размечает объекты из

тредов приложения, объекты в статических полях и

т.д.

• Остановка короткая

12993.795: [GC [1 CMS-initial-mark: 8498366K(11184832K)]

8647061K(12443136K), 0.1610800 secs] [Times: user=0.16 sys=0.00,

real=0.16 secs]

Page 21: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

21

CMS: Mark

• CMS начинает искать достижимые объекты из

«корневых» объектов, найденных на предыдущей

фазе.

• Работает одновременно с приложением (остановки

тредов нет)

12993.957: [CMS-concurrent-mark-start]

12997.311: [CMS-concurrent-mark: 3.352/3.354 secs] [Times: user=19.88

sys=0.20, real=3.36 secs]

Page 22: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

22

CMS: Preclean

• Начинает поиск живых объектов из всех dirty cards

• Работает одновременно с приложением (остановки

тредов нет)

12997.311: [CMS-concurrent-preclean-start]

12997.617: [CMS-concurrent-preclean: 0.298/0.306 secs] [Times:

user=0.64 sys=0.01, real=0.30 secs]

12997.617: [CMS-concurrent-abortable-preclean-start]

12997.747: [GC 12997.747: [ParNew: 1252211K->139776K(1258304K),

0.5655490 secs] 9750577K->9040052K(12443136K), 0.5657350 secs]

[Times: user=7.90 sys=0.02, real=0.56 secs]

CMS: abort preclean due to time 13003.750: [CMS-concurrent-abortable-

preclean: 5.549/6.133 secs] [Times: user=17.06 sys=0.17, real=6.14 secs]

Page 23: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

23

CMS: Remark (rescan)

• STW-стадия после сэмплинга нового поколения

Remark (rescan)13003.752: [GC[YG occupancy: 677602 K (1258304

K)]13003.752: [Rescan (parallel) , 0.3685750 secs]13004.649: [weak refs

processing, 0.0001350 secs] [1 CMS-remark: 8900276K(11184832K)]

9577878K(12443136K), 0.8974050 secs] [Times: user=5.93 sys=0.04,

real=0.90 secs]

Page 24: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

24

CMS: Sweep

• CMS добавляет свободные области во freelists

• Работает одновременно с приложением (остановки

тредов нет)

13004.650: [CMS-concurrent-sweep-start]

13014.364: [CMS-concurrent-sweep: 9.714/9.714 secs] [Times: user=10.08

sys=0.30, real=9.71 secs]

Page 25: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

25

CMS: Reset

• CMS освобождает все свои внутренние объекты и

структуры для дальнейшего использования

13014.364: [CMS-concurrent-reset-start]

13014.391: [CMS-concurrent-reset: 0.028/0.028 secs] [Times: user=0.04

sys=0.00, real=0.03 secs]

Page 26: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

26

Ещё одна опция GC

• Добавим ещё одну опцию логирования GC:

-XX:+PrintTenuringDistribution

Page 27: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

27

PrintTenuringDistribution

• Нормальная строка лога GC после сборки в новом

поколении выглядит примерно так:

146715.578: [GC 146715.578: [ParNew: 1258304K->138199K(1258304K),

0.1885550 secs] 9504038K->8525401K(12443136K), 0.1887600 secs] [Times:

user=2.48 sys=0.01, real=0.19 secs]

Page 28: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

28

PrintTenuringDistribution

• А вот строка лога GC после сборки в новом поколении

и флаге -XX+PrintTenuringDistribution:

2014-01-30T10:47:43.809+0400: [GC [ParNew

Desired survivor size 724775728 bytes, new threshold 15 (max 15)

- age 1: 25185248 bytes, 25185248 total

- age 2: 268810584 bytes, 293995832 total

- age 3: 3159800 bytes, 297155632 total

- age 4: 229752592 bytes, 526908224 total

- age 5: 28050384 bytes, 554958608 total

- age 6: 7744840 bytes, 562703448 total

: 2209979K->656455K(2359296K), 0.4699730 secs] 7615361K-

>6061837K(13893632K), 0.4702490 secs] [Times: user=3.46 sys=0.06,

real=0.47 secs]

Page 29: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

29

2 последовательных вызова GC

• 2014-01-28T14:42:44.102+0400: [GC [ParNew

Desired survivor size 26836992 bytes, new threshold 1 (max 4)

- age 1: 29692568 bytes, 29692568 total

- age 2: 12514000 bytes, 42206568 total

: 461639K->52416K(471872K), 0.0553180 secs] 6447587K-

>6050819K(12530496K), 0.0554520 secs] [Times: user=0.81 sys=0.01,

real=0.06 secs]

2014-01-28T14:42:45.794+0400: [GC [ParNew

Desired survivor size 26836992 bytes, new threshold 1 (max 4)

- age 1: 32051328 bytes, 32051328 total

: 471872K->42579K(471872K), 0.0656970 secs] 6470275K-

>6067036K(12530496K), 0.0658310 secs] [Times: user=0.84 sys=0.01,

real=0.07 secs]

Page 30: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

30

Как уменьшить объёмы миграций из одного

поколения в другое

• Увеличить размер нового поколения

• Увеличить возраст объектов, при котором они могут

мигрировать в tenured generation

• Увеличить survivor space и его threshold

• Тюнить само приложение

Page 31: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

31

Параметры JVM для нового поколения

• -XX:NewSize=3g

• -XX:InitialTenuringThreshold=15

• -XX:MaxTenuringThreshold=15

• -XX:SurvivorRatio=2

• -XX:TargetSurvivorRatio=90

• -XX:-UseAdaptiveSizePolicy

Page 32: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

32

Результат

• Объёмы миграций стали меньше

• Concurrent mode failure стал реже (а именно – раз в

час-полтора)

Page 33: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

33

Что-то делаем с CMS ?

• CMS долго не мог понять, когда ему стартовать…

12993.795: [GC [1 CMS-initial-mark: 8498366K(11184832K)]

8647061K(12443136K), 0.1610800 secs] [Times: user=0.16 sys=0.00,

real=0.16 secs]

• Реально живых объектов – примерно 5.0 гб. И

concurrent mode failure очищает весь хип примерно до

такого объёма (чаще всего)

• Посоветуем CMS запускаться тогда и только тогда,

когда старое поколение будет заполнено на 5.5 гб

Page 34: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

34

Параметры JVM для старого поколения

• -XX:+UseCMSInitiatingOccupancyOnly

• -XX:CMSInitiatingOccupancyFraction=50 – как раз

соответсвует запуску CMS при заполнении tenured

generation на 5.5 гб

• -XX:+CMSScavengeBeforeRemark

Page 35: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

35

Окончательный список параметров

JVM для GC • -Xms14G

• -Xmx14G

• -XX:+UseParNewGC

• -XX:+UseConcMarkSweepGC

• -XX:+UseCMSInitiatingOccupancyOnly

• -XX:CMSInitiatingOccupancyFraction=50

• -XX:-UseAdaptiveSizePolicy

• -XX:+CMSScavengeBeforeRemark

• -XX:NewSize=3g

• -XX:+PrintTenuringDistribution

• -XX:InitialTenuringThreshold=15

• -XX:MaxTenuringThreshold=15

• -XX:SurvivorRatio=2

• -XX:TargetSurvivorRatio=90

Page 36: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

36

Результат

• Concurrent mode failure стал редкостью (раз в день)

• Для нашего приложения это нормально

Page 37: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

37

Один день – это часто

• Возможно, для кого-то один день – это часто. Какие

ещё workarounds возможны?

Page 38: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

38

Переход на JDK 8

• Переходим на JDK 8 и используем G1 GC

• Хороший повод выучить lambda expressions,

java.util.stream package, type annotations и т. д.

• Раз уж заговорили про лямбды, то вспомнить аксиомы

лямбда-исчисления, а также – почему эта формальная

система полна по Тюрингу

Page 39: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

39

Extra-tuning CMS

• Тюнинг, связанный с уменьшением фрагментации

tenured generation

• Смотреть YoungPLABSize, OldPLABSize,

ParallelGCRetainPLAB, TargetPLABWastePct,

PLABWeight, ResizePLAB, PrintPLAB,

CMSParPromoteBlocksToClaim,

CMSPLABRecordAlways

• В общем говоря, смотреть опции, которые выводит: java -server -XX:+PrintFlagsFinal -version 2>/dev/null | grep -i PLAB

Page 40: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

40

Управляем Full GC

• А почему бы не сделать так, чтобы полная сборка (Full GC)

вызывалась в определённое время, когда нагрузка на сервера

минимальная?

• Можно: джоб с одним-единственным вызовом System.gc() 2014-07-21T01:16:04.820-0700: 52.160: [Full GC (System.gc()) 52.160: [CMS:

• Для этого ещё надо:

-XX:-DisableExplicitGC

-XX:-ExplicitGCInvokesConcurrent

(собственно, такие флаги по умолчанию)

• Проблема: все сервера будут недоступны в одно и то же время

• Сразу вопрос: System.gc() ведь всегда плохо?

Page 41: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

41

Управляем Full GC

• Проблема: все сервера будут недоступны в одно и то же время

• Сразу вопрос: System.gc() ведь всегда плохо?

Page 42: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

42

Немного в защиту System.gc()

• “If you observe an explicit full garbage collection in the garbage

collection logs, determine why it is happening and then decide

whether it should be disabled, whether the call should be removed

from the source code, or whether it makes sense to specify an

invocation of a CMS concurrent garbage collection cycle.”

- Charlie Hunt, Binu John, Java performance

Page 43: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

43

Ещё немного в защиту System.gc()

• “Be careful when disabling explicit garbage collection. Doing so may

have a nontrivial performance impact on the Java application. There

may also be situations where timely object reference processing is

required and garbage collections are not happening frequently

enough for that to occur. Applications using Java RMI may be subject

to this. It is advisable when explicitly disabling explicit garbage

collection to have a reason for doing so. Likewise, it is advisable to

have a reason for using System.gc() in an application.”

- Charlie Hunt, Binu John, Java performance

Page 44: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

44

Управляем Full GC

• Можно сделать так, что джоб (с System.gc() внутри) исполнялся

бы с псевдослучайным интервалом

• Кажется, проблема того, что все сервера будут недоступны в

одно и то же время, решена?

• Какова вероятность того, что при таком подходе будет

существовать момент времени, при котором все сервера ушли в

Full GC?

Page 45: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

45

Формализация

• Пусть 𝑙 – общий интервал (например, 86400 сек.), 𝑠 –

длительность Full GC (в секундах), 𝑁 – количество машин

• На каждой из N машин в момент времени 𝑡 ∈ 𝑈 0, 𝑙 запускается

джоб с System.gc(), и полная сборка занимает 𝑠 секунд

• Какова вероятность того, что существует момент времени, в

который на всех машинах Full GC?

Page 46: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

46

Решение

• Вероятность того, что запуск Full GC на первой машине произойдёт в

момент 𝑡 ∈ 𝑠, 𝑙 − 𝑠 , а у остальных – в моменты на промежутках

𝑡 − 𝑠, 𝑡 + 𝑠 , равна 𝑙−2𝑠

𝑙

2𝑠

𝑙

𝑁−1

• Остаётся ещё вариант, когда Full GC на первой машине произойдёт в

момент 𝑡 ∈ 0, 𝑠 или 𝑡 ∈ 𝑙 − 𝑠, 𝑙 . В общем говоря, это событие влечёт за

собой событие, когда на всех машинах Full GC сработал на 0,2𝑠 или

𝑙 − 2𝑠, 𝑙 , вероятность каждого из которых 2𝑠

𝑙

𝑁

• Поэтому:

𝑙 − 2𝑠

𝑙

2𝑠

𝑙

𝑁−1

< 𝑝 𝑁, 𝑙, 𝑠 <𝑙 − 2𝑠

𝑙

2𝑠

𝑙

𝑁−1

+ 22𝑠

𝑙

𝑁

𝑙 − 2𝑠

𝑙

2𝑠

𝑙

𝑁−1

< 𝑝 𝑁, 𝑙, 𝑠 <𝑙 + 2𝑠

𝑙

2𝑠

𝑙

𝑁−1

• Для 𝑁 = 2, 𝑙 = 86400, 𝑠 = 90 эта вероятность между 0.00415 и 0.00418

Page 47: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

47

Ещё бы что-то придумать…

• Разбиваете интервал (один день) на несколько интервалов, равное

количеству машин

• Для каждой из машин забираете с DistributedAtomicLong (Curator

Framework, Apache ZooKeeper) уникальные последовательные значения,

и эссайните каждой машине свой интервал

• Для каждой машины System.gc() запускается только на своём интервале

• Ну и всё… по идее, в каждый момент времени недоступной будет не

более одной машины…

Page 48: Об одной неприятной проблеме сборщика Concurrent Mark-Sweep, Кирилл Голоднов

48

Литература

• Java performance / Charlie Hunt, Binu John

“This book is the definitive masterclass in performance tuning Java applications…” - James Gosling

“This book you are now reading is the most ambitious book on the topic of Java performance that has

ever been written.” - Steve Wilson, VP Engineering, Oracle Corporation

• Java technical notes: http://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

• The Java HotSpot Performance Engine Architecture:

http://www.oracle.com/technetwork/java/whitepaper-135217.html

• Java GC, HotSpot's CMS and heap fragmentation: http://blog.ragozin.info/2011/10/java-cg-

hotspots-cms-and-heap.html

• Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning:

http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html

• Jon Masamitsu's Weblog: https://blogs.oracle.com/jonthecollector/entry/did_you_know

• “abort preclean due to time” in Concurrent Mark & Sweep:

http://mail.openjdk.java.net/pipermail/hotspot-gc-use/2011-May/000862.html

• Concurrent mode failure in practice, part 1:

http://kyryloholodnov.wordpress.com/2014/02/19/concurrent-mode-failure-in-practice-part-1

• Concurrent mode failure in practice, part 2:

http://kyryloholodnov.wordpress.com/2014/05/27/concurrent-mode-failure-in-practice-part-2