Об одной неприятной проблеме сборщика Concurrent Mark-Sweep,...
-
Upload
yandex -
Category
Technology
-
view
712 -
download
3
description
Transcript of Об одной неприятной проблеме сборщика Concurrent Mark-Sweep,...
1
2
Об одной неприятной
проблеме сборщика
Concurrent Mark-Sweep
3
Обо мне
• Java-разработчик сервисов
Яндекс.Метрика (большой метрики,
метрики приложений, внутренних
сервисов метрики)
• Увлекаюсь математикой и прикладными
науками, связанными с ней: машинное
обучение, информационный поиск,
криптография
4
Содержание
• Постановка задачи
• Какие сборщики мусора мы бы могли выбрать
• Почему мы выбрали Concurrent Mark-Sweep и
как он работает
• Как мы тюнили сборки в новом и старом
поколениях
• Что ещё можно было бы придумать
5
Постановка задачи
• Есть N машин, на которых развёрнуто Java-приложение
• Приложение имеет внутренний сервис (джоб) – сотни тредов,
high concurrency, большие объёмы обрабатываемых данных
• В каждый момент времени должно работать не более одного
джоба, поэтому N машин соединены координатором Apache
Curator Framework (АПИ над Apache ZooKeeper)
• Проблема: координатор очень часто переключает выполнение
джоба с машины на машину, джоб едва ли успевает полностью
отработать на какой-то из машин
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 минут…
7
Какой у нас Java Heap Space?
• Находим наш процесс через что-то типа
ps –ef | grep java
jps -v
и смотрим на параметры -Xms и -Xmx
• Если этих параметров нет, то используем:
jconsole, или
kill -3 <pid>
• У нас хип на 14 гб: -Xms1G -Xmx14G
8
Java Heap Space
9
Какие у нас алгоритмы GC?
• Настройки GC:
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
10
Расширенные настройки
• -Xloggc:gc.log
• -XX:+PrintGCDetails
• -XX:+PrintGCDateStamps
• -XX:-PrintGCTimeStamps
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]
12
PrintGCTimeStamps
• Почему надо -XX:-PrintGCTimeStamps:
2014-07-20T09:54:31.478-0700: 208.268: [GC (Allocation Failure)
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]
14
Что такое concurrent mode failure
• CMS (в основном) работает параллельно с тредами приложения
• Из-за этого может быть 2 ситуации:
- Tenured generation наполняется быстрее, чем CMS освобождает
там память. В итоге, рано или поздно, из young generation надо
перенести больший объём (в байтах), чем свободно в tenured
generation
- В tenured generation есть необходимое свободное место, но оно
сильно фрагментировано
• Обе ситуации генерируют особое исключение CMS – concurrent
mode failure. По сути, это однопоточный(!!!) Full GC.
15
Почему бы не использовать другие
сборщики? • Serial Collector
• Parallel Collector
• Garbage-First (G1) Garbage Collector
16
Serial collector
• STW collector… и слишком долго работает…
17
G1 GC
• Неплохая замена CMS, но полная поддержка
появилась только в Oracle HotSpot 7 update 4
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
19
Как работает CMS
- Initial mark – STW
- Mark
- Preclean
- Remark – STW
- Sweep
- Reset
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]
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]
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]
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]
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]
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]
26
Ещё одна опция GC
• Добавим ещё одну опцию логирования GC:
-XX:+PrintTenuringDistribution
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]
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]
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]
30
Как уменьшить объёмы миграций из одного
поколения в другое
• Увеличить размер нового поколения
• Увеличить возраст объектов, при котором они могут
мигрировать в tenured generation
• Увеличить survivor space и его threshold
• Тюнить само приложение
31
Параметры JVM для нового поколения
• -XX:NewSize=3g
• -XX:InitialTenuringThreshold=15
• -XX:MaxTenuringThreshold=15
• -XX:SurvivorRatio=2
• -XX:TargetSurvivorRatio=90
• -XX:-UseAdaptiveSizePolicy
32
Результат
• Объёмы миграций стали меньше
• Concurrent mode failure стал реже (а именно – раз в
час-полтора)
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 гб
34
Параметры JVM для старого поколения
• -XX:+UseCMSInitiatingOccupancyOnly
• -XX:CMSInitiatingOccupancyFraction=50 – как раз
соответсвует запуску CMS при заполнении tenured
generation на 5.5 гб
• -XX:+CMSScavengeBeforeRemark
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
36
Результат
• Concurrent mode failure стал редкостью (раз в день)
• Для нашего приложения это нормально
37
Один день – это часто
• Возможно, для кого-то один день – это часто. Какие
ещё workarounds возможны?
38
Переход на JDK 8
• Переходим на JDK 8 и используем G1 GC
• Хороший повод выучить lambda expressions,
java.util.stream package, type annotations и т. д.
• Раз уж заговорили про лямбды, то вспомнить аксиомы
лямбда-исчисления, а также – почему эта формальная
система полна по Тюрингу
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
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() ведь всегда плохо?
41
Управляем Full GC
• Проблема: все сервера будут недоступны в одно и то же время
• Сразу вопрос: System.gc() ведь всегда плохо?
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
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
44
Управляем Full GC
• Можно сделать так, что джоб (с System.gc() внутри) исполнялся
бы с псевдослучайным интервалом
• Кажется, проблема того, что все сервера будут недоступны в
одно и то же время, решена?
• Какова вероятность того, что при таком подходе будет
существовать момент времени, при котором все сервера ушли в
Full GC?
45
Формализация
• Пусть 𝑙 – общий интервал (например, 86400 сек.), 𝑠 –
длительность Full GC (в секундах), 𝑁 – количество машин
• На каждой из N машин в момент времени 𝑡 ∈ 𝑈 0, 𝑙 запускается
джоб с System.gc(), и полная сборка занимает 𝑠 секунд
• Какова вероятность того, что существует момент времени, в
который на всех машинах Full GC?
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
47
Ещё бы что-то придумать…
• Разбиваете интервал (один день) на несколько интервалов, равное
количеству машин
• Для каждой из машин забираете с DistributedAtomicLong (Curator
Framework, Apache ZooKeeper) уникальные последовательные значения,
и эссайните каждой машине свой интервал
• Для каждой машины System.gc() запускается только на своём интервале
• Ну и всё… по идее, в каждый момент времени недоступной будет не
более одной машины…
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