Comments 42
— Название цикла очень невнятное. Когда времени мало и пробегаешь глазами по списку популярного за сутки, то большая вероятность, что часть релевантной аудитории просто пропустит такую статью.
— В который раз буду спорить, что «конкурентный» — неверный перевод слова «concurrent».
Название цикла очень невнятное.
Между названием, которое принесет больше посетителей, и названием, которое мне нравится, я всегда выбираю второе. Я плохой smm-щик )
«конкурентный» — неверный перевод слова «concurrent»
Готов исправить в статье этот термин, если предложите более подходящий в данном конексте вариант.
Но по сути это все уже мелочи жизни, главное все поняли, о чем идет речь.
Вот небольшое пояснение разницы Concurrency vs Parallelism
Threads which run concurrently do not always run simultaneously (eg. 2 threads on 1 core)… although to the end user they may well appear to – provided the cpu switches between them quickly enough.
Два потока на одном ядре, которое вынуждено между ними переключаться — это и есть конкуренция за ресурсы процессора.
Вы также можете открыть официальную документацию Oracle и обратить внимание на использование слова concurrent в ней. Особенно на секцию Concurrent Phases, в которой, как можно понять из названия, речь как раз об этой особенности сборщика. В ней всего несколько предложений и все они про одно и то же — конкуренцию потоков сборщика с потоками основной программы за ресурсы процессора, в результате которой уменьшается общая пропускная способность. Об изменении памяти там ни слова.
И чтобы два раза по ссылке не ходить, поищите сразу там же использование слова parallel. Оно всегда используется исключительно для обозначения параллельного сборщика. Чтобы не вносить путаницу. Я тоже стараюсь не вносить путаницу. И дополнительно всегда стараюсь приводить оригинальные английские термины, чтобы при необходимости можно было разобраться, о чем речь.
Я плохой smm-щик )
SMS знаю, MMM знаю, SMM — не знаю )
Готов исправить в статье этот термин, если предложите более подходящий в данном конексте вариант.
Как насчет «одновременный»? Насчет же конкуренции, параллельный сборщик (parallel) при прочих равных будет конкурировать с основными потоками еще больше, чем ваш конкурентный, потому что у параллельного больше своих потоков. Поэтому, с вашей точки зрения, это параллельный сборщик надо называть конкурентным. :)
Теперь давайте взглянем на наши сборщики, в которых два этих понятия используются абсолютно правильно:
Параллельный сборщик. Он а) на время выполнения сборки мусора останавливает все основные потоки приложения, б) по умолчанию оперирует количеством потоков, равным или меньшим количеству процессорных ядер и в) при переносе выживших объектов выделяет отдельную область памяти для каждого потока. Об этом подробно рассказано в предыдущей статье. То есть потоки этого сборщика реально работают параллельно (у каждого свое ядро процессора) и не конкурируют ни с основными потоками приложения (которые остановлены на время сборки) ни за память (которая разделена на непересекающиеся части). Чистейшей воды параллельное исполнение, отсюда и название сборщика.
Конкурентные сборщики. Они, в отличие от параллельного сборщика, часть своей работы выполняют без остановки основных процессов приложения (см. описание в статье), поэтому реально конкурируют с ними за ресурсы процессора. Поэтому и в упомянутой статье CMS приведен в качестве примера корректного использования слова concurrent.
То есть использование названия concurrent и его перевода как «конкурентный» оправдано для CMS/G1 и не может иметь отношения к Parallel GC. При этом использование названия parallel и его перевода как «параллельный» абсолютно оправдано для Parallel GC и не может относиться с CMS/G1.
В свете вышесказанного считаю, что «конкурентный» подходит больше, чем «одновременный».
SMM — Social Media Marketing.
Согласен, что я упустил эту «деталь» параллельного сборщика, который вообще останавливает рабочие потоки. Это очень важно.
Но в вашем варианте: конкурентный сборщик потому, что конкурирует с приложением, то есть название на основе взаимодействия сборщика и приложения. А параллельный потому, что его собственные потоки работают параллельно, то есть название на основе его внутренней архитектуры, а не его взаимодействия с приложением. Но давайте будем последовательны — если параллельный сборщик назвать в терминах взаимодействия с приложением, то он будет «останавливающий». Ну или «многопоточный останавливающий». Согласен, что и английские названия выбраны неудачно, потому что они не последовательны. Но ваш вариант с конкурентным только еще больше запутывает картину.
Попробуйте взглянуть на мир не глазами разработчика, не глазами приложения, а глазами сборщика. Он бодрствует только во время выполнения своих обязанностей, а остальное время спит и ничего не видит. Как же выглядит видимая ему жизнь?
Сборщик просыпается, видит кучу мусора и не торопясь, в одном потоке, его собирает, после чего засыпает. Как его назовем? Пусть будет Serial GC, ведь всё последовательно и нужно с чего-то начать.
Проходят годы, появляется второй тип сборщика. Он просыпается, видит кучу мусора и разбирает ее, но уже используя несколько потоков, работающих параллельно. Как назовем такой сборщик? Логично назвать Parallel GC, нет?
Предыдущий сборщик работает быстро, но даже его короткие паузы устраивают не все приложения, и появляется новый тип сборщика, который во время некоторых фаз своего бодрствования обнаруживает, что, оказывается, кроме него есть еще и другие потоки (приложения), с которыми ему приходится работать в конкурентом режиме. Что это за фазы такие? Это фазы Mark и Sweep. Как назовем такой сборщик, у которого фазы Mark и Sweep проходят в конкурентном режиме? Не самым плохим вариантом будет Concurrent Mark Sweep. Длинно. Сократим до CMS.
Годы идут, объемы данных растут, требования к задержкам ужесточаются, но и эволюция не стоит на месте. Появляется сборщик, который, помимо того, что имеет конкурентные фазы, еще и может выбирать, какую часть вверенного ему пространства памяти убирать, а какую оставить на потом. И он, как мудрый сборщик, решает в первую очередь убирать то, что больше всего замусорено. Как назовем такой сборщик? Давайте Garbage First, вроде не самый плохой вариант. Но тоже длинно, сократим до GF? Нее, все будут смеяться, что это girlfriend. Над G1 так не посмеешься, да и выглядит современнее, остановимся на этом варианте.
Два последних сборщика, в отличие от первых, имеют фазы конкурентного выполнения. Как бы их назвать собирательно? Конкурентные. Не потому что они так всегда взаимодействуют с основным приложением. А потому что у них есть конкурентные фазы, а у других нет, считаете не логично? Вы ведь протягивая 50 рублей продавщице мороженого и говоря «мне ореховое» не ожидаете (надеюсь), что она вам большой орех на палочке даст. И правильно, она вам его не даст, а даст мороженое с кусочками орехов.
Я, к сожалению или к счастью, не имею отношения к разработке сборщиков мусора и к их именованию, поэтому не могу знать наверняка, чем руководствовались те, кто имеет. Может, они думали так, как указано выше. А может и нет. Вы можете придираться к исходным названиям сколько угодно, но эти вопросы вряд ли стоит адресовывать мне. Я перевел эти термины и считаю выбранный перевод наиболее подходящим и отражающим то, что закладывалось в исходные названия их авторами. Почему, объяснил уже не один раз.
А предложение перевести Parallel GC как «многопоточный останавливающий» с одновременным упреком в путанице и в отсутствии логики в исходных названиях вряд ли можно рассматривать всерьез. Останавливают выполнение потоков приложения все (все!) сборщики, поэтому и раздел «Ситуации STW» есть в описании каждого из них. Многопоточными являются три из четырех. «Однопоточный останавливающий», «многопоточный останавливающий без конкурентного выполнения», «многопоточный останавливающий с конкурентным выполнением фаз Mark и Sweep», «многопоточный останавливающий с конкурентными фазами и дробными поколениями». Так будет звучать ваше предложение о переводе терминов Serial / Parallel / CMS / G1 полностью?
А с таким подходом к выискиванию несоответствий вы можете бесконечно спорить с кем угодно и о чем угодно. Начать можно вообще с самого термина «garbage collector». Собиратель? Коллекционер? Коллектор? Да мы вообще мусор никуда не собираем и не коллекционируем, мы его просто удаляем, поэтому давайте не будем запутывать картину: garbage deleter! Ах да, если уж разбираться всерьез, то на самом деле мы его даже не удаляем, а просто оставляем где был и игнорируем. Может, garbage ignorer?
И так вы можете до бесконечности. А я, пожалуй, воздержусь до появления реальных, полностью продуманных предложений.
Однако, если в аудитории, не знающей про детали реализации сборщиков, спросисть: «конкурирующий сборщик — это такой, который конкурирует с потоками проложения. Вопрос: а что такое параллельный сборщик?» То ответ будет однозначным: «это такой сборщик, которой не конкурирует, а работает параллельно».
В английском варианте, «одновременный» и «параллельный», чувствуется некая похожесть терминов, поэтому заранее понятно, что есть подвох и особой логики тут искать не стоит, а надо разобраться и просто запомнить названия. В вашем варианте ложная логика явно присутствует.
Кстати, кто сказал, что параллельный сборщек не конкурирует сам с собой? В системе обычно сотни и тысячи активных потоков. Ну будет сотни + потоки сборщика.
Ну и про пример с мороженным, он не полный. Полный вот такой:
— Дайте мне ореховое. Дают ореховое.
— Дайте мне банановое. Дают банан.
Почему? А потому что. ;)
Я же объяснил логику, скрытую за этими терминами, и выбрал для них соответствующие этой логике переводы. Если вы сомневаетесь, что я правильно понял, что имелось в виду под терминами «parallel» и «concurrent» в именах сборщиков, то приглашаю вас почитать официальную документацию Oracle, обращая внимание на использование этих терминов. Они используются ровно так, ровно в том виде и ровно в том значении, что я описал.
И никаких реальных, более корректных альтернатив вы опять не предлагаете. Честно говоря, не вижу смысла продолжать диалог в таком русле.
Пример с мороженым, кстати, всего лишь доказывает право на существование такого подхода к именованию, а не его единственность и обязательность его использования везде и всегда. Так что ваше дополнение некорректно.
Parallel и Concurrent — почти одинаковые термины, часто взаимозаменяются, поэтому тут понятно, что они обознают не различия между подходами к сборке мусора, а просто некие особености. Они, можно сказать, почти имена собственные в данном случае.
Конкурентный и параллельный — совсем не одинаковые термины, во многом даже противоположные, если используются парой, поэтому ошибочно заставляют думать, что они определяют главное различие между разными сборщиками.
Я предложил «одновременный». Согласен, не очень удачный термин, но он хотя бы сохраняет исходную сожесть между concurrent и parallel.
он старается разносить во времени малые и старшие сборки мусора, чтобы они совместно не создавали продолжительных пауз в работе приложенияНа самом деле, ровно наоборот. Перед запуском CMS цикла JVM по возможности дожидается сборки в молодом поколении (
-XX:CMSWaitDuration
), чтобы уменьшить время Initial mark. Аналогично перед remark фазой зачастую просят CMS опять дождаться сборки в молодом поколении (-XX:+CMSScavengeBeforeRemark
), чтобы уменьшить длительность remark.серьезно рассматривают его на роль сборщика по умолчаниюУже сделали.
-XX:AggressiveOptsНе надо советовать эту опцию. Во-первых, она не имеет отношения к GC, во-вторых, не предназначена для использования в production, и порой даже вредна.
На самом деле, ровно наоборот.
Я бы не сказал, что ровно наоборот. На самом деле, ситуация просто сложнее: CMS пытается как можно дальше разнести по времени паузы remark (так как она большая) и паузу малой сборки. С паузой initial mark он этого делать, действительно, не пытается. Так, по крайней мере, утверждает официальная документация Oracle: Concurrent Mark Sweep Collector (секция Scheduling Pauses). И да, согласен, чем ближе initial mark к последней малой сборке, тем эффективнее будет последующая большая сборка.
Уже сделали.
Ну, JDK 9 еще не выпущен. А в соответствующей задаче они оставляют себе место для отступления.
Не надо советовать эту опцию.
Я не советовал, я сообщил о ее наличии и предупредил, что это экспериментальная функциональность. Но иногда ее советуют даже сами разработчики из Oracle (см. ссылку с примером Solr) и она используется в пром. средах. Конечно, согласен, что это исключение из правил, и эта опция определенно может быть вредна. Но в некоторых случаях вредной может быть практически любая из перечисленных опций. Я все же полагаюсь на разумность читателей )
Меня вот такие записи смутили
[Full GC 2171M->671M(6144M), 4.1407616 secs]
[Eden: 290.0M(496.0M)->0.0B(814.0M) Survivors: 40.0M->0.0B Heap: 2171.3M(6144.0M)->671.9M(6144.0M)], [Perm: 524287K->252914K
(253952K)]
[Times: user=5.05 sys=0.42, real=4.13 secs]
Сборку нужно в динамике наблюдать. По одному этому примеру видно только, что в старшем поколении у вас, действительно, очень много мусорных данных (освобождено ~1.5 ГБ за сборку, из которых больше гигабайта из старшего поколения). Если вы такую же картину наблюдаете во всех сборках, то нужно перенастраивать распределение кучи между младшим и старшим поколениями. Хотя по этому отрывку лога видно, что G1 уже сам начал это делать, увеличив размер младшего поколения. Скорее всего, следующая сборка будет уже лучше. Как я сказал, нужно в динамике наблюдать и дать время сборщику подстроиться под ваше приложение.
А правильно ли я понимаю, что G1 во время малой сборки приостанавливает отдельные потоки?
Тогда вобщем-то понятно из-за чего происходило торможение. GC останавливает поток, работающий в synchronized-методе, а за ним останавливаются и многие другие потоки на входе в этот метод. В результате получается, что каждая малая сборка работает как stop-the-world.
Но имейте в виду, что тут может быть очень много нюансов. Возможно, в указанной сборке учитываются не все постоянные объекты. Возможно, у вас есть долгоиграющие методы, которым нужен такой большой объем памяти, поэтому его нельзя уменьшать. Возможно, у вас периодически создаются большие объекты (больше нескольких мегабайт), которые приходится размещать в Tenured, минуя Eden. И еще много чего может сыграть свою роль. Тут в комментарии я все не смогу перечислить подробно. Постараюсь как можно больше описать в следующей статье.
может наоброт — не фрагментирует? дефрагментация — процесс устранения фрагментации 8)
аналогичная ошибка в предыдущей статье
Спасибо за подробную статью! Можете подсказать смысл этого предложения -
Одним из побочных эффектов такого разделения является то, что все
объекты младшего поколения (даже потенциально мертвые) могут играть роль
корней при определении статуса объектов в старшем поколении.
Как объекты младшего поколения могу играть роль корней?
Так как в CMS существуют очистки только старшего поколения (без очистки младшего), могут возникать такие ситуации:
У вас существует уже давно перенесенный в старшее поколение объект (назовем его
O
), на который существуют ссылки только из других объектов старшего поколения.Вы создали новый объект (назовем его
Y
), который был размещен в младшем поколении. При конструировании этого объекта вы передали ему ссылку наO
, которую он сохранил в одном из своих полей.Все остальные ссылки на объект
O
были "забыты", теперьY
является единственным обладателем ссылки на него.В этот момент была инициирована сборка старшего поколения.
И вот тут возникает та самая ситуация, о которой идет речь в приведенном абзаце. На объект O
нет ссылок из "классических" корней -- ни на стеке, ни из статических полей каких-либо классов ссылок на него не осталось. Но при этом объект O
не должен считаться мертвым, ведь на него есть ссылка из размещенного в младшем поколении объекта Y
. То есть поле объекта Y
, размещенного в младшем поколении, выступает в роли корня.
В обычной полной сборке у вас корнем выступала бы ссылка на стеке напрямую на Y
(если бы сборка застала нас в тот момент, когда Y
был только создан и у нас была бы локальная переменная со ссылкой на него), или на какой-нибудь другой объект, который бы хранил ссылку на Y
и т. п. Цепочка от корня до O
была бы длиннее. Но для ее составления требовался бы анализ младшего поколения, который является частью полной сборки.
А так как в CMS в данном случае мы рассматриваем не полную сборку, такой анализ не производится, а просто все объекты в младшем поколении считаются "корнями". Если говорить совсем формально, то корнями являются поля-ссылки таких объектов, а не сами объекты, но сути это не меняет.
Очень крутые статьи, но некоторые утверждения вводят в заблуждение, имхо. Например, часто встречается, в частности в определении терминов mark-sweep, утверждение будто сборщики "помечают выжившие объекты и удаляют мертвые". Скорее они помечают достижимые и перемещают их. Ничего при этом не удаляют и никак с недостижимыми(мертвыми) не взаимодействуют. А тут складывается ложное мнение о работе сборщика. Причем это встречается и в первых статьях.
По факту у нас нет "сборки мусора" в привычном понимании, а скорее лишь "сборка и перемещение достижимых объектов"
Спасибо за отзыв и за интересную поднятую тему, которая позволит лишний раз копнуть чуть глубже.
Вы абсолютно правы, в большинстве случаев сборщики ничего не удаляют, а только двигают туда-сюда достижимые (они же "выжившие") объекты, поэтому термин "удаление объекта" может выглядеть не самым удачным для описания их работы.
Но в продвинутых GC, как это часто бывает у нас в компьютерах, всё сложннее, чем может показаться с первого взгляда. Давайте отложим самые распространенные сценарии выделения / высвобождения памяти и вспомним, например, про существование SoftReference и WeakReference. В отношении них GC имеет больше свободы и может самостоятельно удалить на них ссылку и перевести из разряда достижимых в мусор. Добавим к этому умение современных GC возвращать память операционной системе после уплотнения объектов. В итоге получим, что в некоторых случаях GC может реально удалить даже достижимый объект (вернуть размещающую его страницу памяти операционке), не то что мусор.
Если же говорить не про технические детали и не пытаться всё формализовать, то можно заметить, что термин "удаление объектов" просто давно уже прижился в описании сборщиков (документация Oracle, RedHat, IBM и практически любая другая). Как, например, "удаление файла" на диске или "удаление записи" в БД, которые на деле практически никогда не приводят к реальному удалению соответствующих данных.
Хотя, повторюсь, ваше замечание в целом верное. Может я даже подправлю текст в некоторых местах при очередной ревизии.
Дюк, вынеси мусор! — 3. CMS и G1