Garbage Collector. Tracing.
Этот подход поиска мусора в Java вводит новое понятие – GC Root или корень, якорь (чуть дальше я поясню, что это такое).
Главную идею подхода можно сформулировать как:
Живые объекты – это те, до которых мы можем добраться от корня (GC Root), в то время как все остальные являются мусором.
Всё, что доступно с живого объекта, – также живое.
Пусть у нас есть следующий код:
Person p = new Person();
p.setFlat(new Flat());
p.setCar(new Car());
p.getCar().setEngine(new Engine());
p.getCar().setHorn(new Horn());
Применимо к тому, о чем мы сейчас говорим, выглядеть это будет как на картинках.
Так вот Person – это и есть тот самый корень, якорь. Т.е это наивысшая точка графа связанных объектов.
Так как Person у нас является живым объектом, то считается, что все объекты, до которых мы можем добраться из Person – также живые.
О том, какие бывают GC Root поговорим в следующем посте.
Этот подход поиска мусора в Java вводит новое понятие – GC Root или корень, якорь (чуть дальше я поясню, что это такое).
Главную идею подхода можно сформулировать как:
Живые объекты – это те, до которых мы можем добраться от корня (GC Root), в то время как все остальные являются мусором.
Всё, что доступно с живого объекта, – также живое.
Пусть у нас есть следующий код:
Person p = new Person();
p.setFlat(new Flat());
p.setCar(new Car());
p.getCar().setEngine(new Engine());
p.getCar().setHorn(new Horn());
Применимо к тому, о чем мы сейчас говорим, выглядеть это будет как на картинках.
Так вот Person – это и есть тот самый корень, якорь. Т.е это наивысшая точка графа связанных объектов.
Так как Person у нас является живым объектом, то считается, что все объекты, до которых мы можем добраться из Person – также живые.
О том, какие бывают GC Root поговорим в следующем посте.
🔥4✍2👍2🤓1👨💻1🫡1
На собеседованиях Java–разработчиков и стажеров часто спрашивают о том, какие бывают GC Root?
Как мы уже поняли из предыдущего поста с примером c Person – локальные переменные являются GC Root.
Компилятор вычисляет для всех переменных live ranges – это всё, что находится на любых путях исполнения от определения переменной до последнего использования (или использований, они могут быть разные на разных путях).
Другими словами, компилятор всегда знает: жива сейчас переменная или нет, могут ее потенциально в будущем еще использовать или нет.
GC тоже ориентируется на это знание от компилятора. Если переменная вне своего live range, то значит она уже не будет корнем.
Но что еще может быть корневой точкой?
Корневой точкой могут быть:
– Локальные переменные и параметры методов;
– Потоки Java;
– Статические переменные;
– Ссылки из JNI.
Из этого следует, что даже самое простое java приложение имеет следующие GC Root:
– Локальные переменные внутри main метода;
– Статические переменные класса, содержащего main метод;
– Параметры main метода;
– Поток, который выполняет main метод.
Как мы уже поняли из предыдущего поста с примером c Person – локальные переменные являются GC Root.
Компилятор вычисляет для всех переменных live ranges – это всё, что находится на любых путях исполнения от определения переменной до последнего использования (или использований, они могут быть разные на разных путях).
Другими словами, компилятор всегда знает: жива сейчас переменная или нет, могут ее потенциально в будущем еще использовать или нет.
GC тоже ориентируется на это знание от компилятора. Если переменная вне своего live range, то значит она уже не будет корнем.
Но что еще может быть корневой точкой?
Корневой точкой могут быть:
– Локальные переменные и параметры методов;
– Потоки Java;
– Статические переменные;
– Ссылки из JNI.
Из этого следует, что даже самое простое java приложение имеет следующие GC Root:
– Локальные переменные внутри main метода;
– Статические переменные класса, содержащего main метод;
– Параметры main метода;
– Поток, который выполняет main метод.
👍5❤1⚡1✍1🤩1👨💻1🫡1
Алгоритмы очистки памяти. Copying collectors.
Очистка памяти процесс довольно сложный, поэтому было также разработано несколько алгоритмов, выполняющих эту задачу.
Рассмотрим Copying collectors.
Память условно делится на две области: from–space и to–space.
Все объекты создаются в области from–space, по мере заполнения этой области запускается очистка мусора. Приложение полностью останавливается – происходит так называемый stop–the–world – в момент начала очистки, после чего все "живые" объекты в from–space копируются в to–space.
После того, как все "живые" объекты скопированы происходит полная очистка from–space и области меняются местами.
Stop–the–World – это остановка любой мутирующей heap активности.
Из плюсов можно выделить то, что объекты плотно забивают память, поэтому tracing происходит быстрее.
Из минусов можно отметить полную остановку приложения и то, что у нас одна область памяти, по сути, не используется, а при большом количестве объектов это проблема.
Для тех приложений, где пауза критична существует такое понятие как инкрементальная сборка. Там мы делаем большое количество кратковременных пауз. Это выражается в большей нагрузке на приложение.
Примером инкрементального сборщика может являться CMS GC.
Перечисленные минусы довольно весомые, поэтому сейчас данный алгоритм практически не используется.
Очистка памяти процесс довольно сложный, поэтому было также разработано несколько алгоритмов, выполняющих эту задачу.
Рассмотрим Copying collectors.
Память условно делится на две области: from–space и to–space.
Все объекты создаются в области from–space, по мере заполнения этой области запускается очистка мусора. Приложение полностью останавливается – происходит так называемый stop–the–world – в момент начала очистки, после чего все "живые" объекты в from–space копируются в to–space.
После того, как все "живые" объекты скопированы происходит полная очистка from–space и области меняются местами.
Stop–the–World – это остановка любой мутирующей heap активности.
Из плюсов можно выделить то, что объекты плотно забивают память, поэтому tracing происходит быстрее.
Из минусов можно отметить полную остановку приложения и то, что у нас одна область памяти, по сути, не используется, а при большом количестве объектов это проблема.
Для тех приложений, где пауза критична существует такое понятие как инкрементальная сборка. Там мы делаем большое количество кратковременных пауз. Это выражается в большей нагрузке на приложение.
Примером инкрементального сборщика может являться CMS GC.
Перечисленные минусы довольно весомые, поэтому сейчас данный алгоритм практически не используется.
👍5❤2🔥2✍1🫡1🆒1
Алгоритмы очистки памяти. Mark–and–Sweep.
Данный алгоритм называется Mark–and–Sweep – "отслеживание и очистка".
Алгоритм очень похож на предыдущий, но с некоторыми улучшениями.
Объекты аллоцируются в памяти и в какой-то момент запускается очистка мусора. Приложение полностью останавливается – здесь все также, как и в предыдущем случае, без остановки никуда.
После остановки мы проходим по всем объектам и помечаем (mark) все "живые" объекты, после чего делаем sweep – чистим и снимаем все пометки с "живых" объектов.
Главным минусом подхода является то, что память становится фрагментированной. Так как получаются целые куски свободной памяти после sweep.
Также при большом количестве "живых" объектов работа алгоритма становится гораздо менее эффективной.
Проиллюстрируем это, красным выделена очищенная область – мусор.
Данный алгоритм называется Mark–and–Sweep – "отслеживание и очистка".
Алгоритм очень похож на предыдущий, но с некоторыми улучшениями.
Объекты аллоцируются в памяти и в какой-то момент запускается очистка мусора. Приложение полностью останавливается – здесь все также, как и в предыдущем случае, без остановки никуда.
После остановки мы проходим по всем объектам и помечаем (mark) все "живые" объекты, после чего делаем sweep – чистим и снимаем все пометки с "живых" объектов.
Главным минусом подхода является то, что память становится фрагментированной. Так как получаются целые куски свободной памяти после sweep.
Также при большом количестве "живых" объектов работа алгоритма становится гораздо менее эффективной.
Проиллюстрируем это, красным выделена очищенная область – мусор.
👍3❤2⚡1✍1🔥1🤓1🫡1
Алгоритмы очистки памяти. Mark–and–Sweep Compact.
В отличии от простого Mark–and–sweep мы ищем "мертвые" объекты, помечаем их для переноса и только после этого останавливаем приложение для очистки памяти.
Так как с "мертвыми" объектами наше приложение уже не работает мы можем искать их параллельно работе приложения. Это очень эффективно, так как мы теперь не тратим время паузы на поиск, как в предыдущих алгоритмах.
После завершения процедуры удаления происходит compact – мы дефрагментируем память. Объекты "сдвигаются" на более близкие адреса.
Плюсы:
– Нет фрагментации памяти;
– Эффективная работа при большом количестве "живых" объектов.
Минусы:
– Плохо работает при большом количестве "мертвых" объектов;
– Compact – дорогостояющая операция, занимающая много времени.
В отличии от простого Mark–and–sweep мы ищем "мертвые" объекты, помечаем их для переноса и только после этого останавливаем приложение для очистки памяти.
Так как с "мертвыми" объектами наше приложение уже не работает мы можем искать их параллельно работе приложения. Это очень эффективно, так как мы теперь не тратим время паузы на поиск, как в предыдущих алгоритмах.
После завершения процедуры удаления происходит compact – мы дефрагментируем память. Объекты "сдвигаются" на более близкие адреса.
Плюсы:
– Нет фрагментации памяти;
– Эффективная работа при большом количестве "живых" объектов.
Минусы:
– Плохо работает при большом количестве "мертвых" объектов;
– Compact – дорогостояющая операция, занимающая много времени.
👍3🔥3❤1✍1⚡1👨💻1🫡1
Реализации Garbage Collector.
– Serial GC
Это последовательная сборка молодого и старого поколения в области памяти Java.
– Parallel GC
Работает также как и Serial GC, но с использованием многопоточности.
– CMS GC (Concurrent Mark–and–Sweep)
Для сборки мусора задействуются несколько потоков, и происходит это через такой же алгоритм, как в Parallel GC.
Использовался до Java 7 и G1.
– G1 GC
Был задуман как замена CMS и разрабатывался для многопоточных приложений, которые характеризуются крупным размером кучи (более 4 ГБ).
– Epsilon
Был выпущен как часть JDK 11. Не реализует никакого реального механизма восстановления памяти. Как только доступная куча исчерпана, JVM завершает работу.
– Shenandoah
Выпущен как часть JDK 12. Ключевое преимущество перед G1 в том, что G1 может эвакуировать области кучи только тогда, когда приложение приостановлено, а Shenandoah перемещает объекты одновременно с приложением.
– ZGC
Выпущен как часть JDK 11 и улучшен в JDK 12. Предназначен для приложений, требующих низкой задержки (паузы в менее чем 10 мс) или задействующих очень большую кучу (несколько терабайт).
– Serial GC
Это последовательная сборка молодого и старого поколения в области памяти Java.
– Parallel GC
Работает также как и Serial GC, но с использованием многопоточности.
– CMS GC (Concurrent Mark–and–Sweep)
Для сборки мусора задействуются несколько потоков, и происходит это через такой же алгоритм, как в Parallel GC.
Использовался до Java 7 и G1.
– G1 GC
Был задуман как замена CMS и разрабатывался для многопоточных приложений, которые характеризуются крупным размером кучи (более 4 ГБ).
– Epsilon
Был выпущен как часть JDK 11. Не реализует никакого реального механизма восстановления памяти. Как только доступная куча исчерпана, JVM завершает работу.
– Shenandoah
Выпущен как часть JDK 12. Ключевое преимущество перед G1 в том, что G1 может эвакуировать области кучи только тогда, когда приложение приостановлено, а Shenandoah перемещает объекты одновременно с приложением.
– ZGC
Выпущен как часть JDK 11 и улучшен в JDK 12. Предназначен для приложений, требующих низкой задержки (паузы в менее чем 10 мс) или задействующих очень большую кучу (несколько терабайт).
👍4🔥4✍1👨💻1🫡1
Цикл while в Java.
Этот цикл в Java структурно выглядит так:
while (expression) {
statement(s)
}
Здесь:
expression – условие цикла, выражение, которое должно возвращать boolean значение.
statement(s) – тело цикла (одна или более строк кода).
Перед каждой итерацией будет вычисляться значение выражения expression. Если результатом выражения будет true, выполняется тело цикла – statement(s).
Этот цикл в Java структурно выглядит так:
while (expression) {
statement(s)
}
Здесь:
expression – условие цикла, выражение, которое должно возвращать boolean значение.
statement(s) – тело цикла (одна или более строк кода).
Перед каждой итерацией будет вычисляться значение выражения expression. Если результатом выражения будет true, выполняется тело цикла – statement(s).
👍6🔥2🎄2⚡1👌1👨💻1
Цикл do..while в Java.
Структура do.. while выглядит так:
do {
statement(s)
} while (expression);
Здесь:
expression – условие цикла, выражение, которое должно возвращать boolean значение.
statement(s) – тело цикла (одна или более строк кода).
В отличие от while, значение expression будет вычисляться после каждой итерации. Если результатом выражения будет true, в очередной раз выполнится тело цикла – statement(s) (как минимум раз).
Структура do.. while выглядит так:
do {
statement(s)
} while (expression);
Здесь:
expression – условие цикла, выражение, которое должно возвращать boolean значение.
statement(s) – тело цикла (одна или более строк кода).
В отличие от while, значение expression будет вычисляться после каждой итерации. Если результатом выражения будет true, в очередной раз выполнится тело цикла – statement(s) (как минимум раз).
👍4✍2🔥2⚡1🥰1👌1👨💻1🫡1🆒1
Цикл for в Java.
Этот Java цикл выглядит так:
for (initialization; termination; increment) {
statement(s)
}
Здесь:
initialization – выражение, которое инициализирует выполнение цикла. Исполняется только раз в начале цикла. Чаще всего в данном выражении инициализируют счетчик цикла.
termination – boolean выражение, которое регулирует окончание выполнения цикла. Если результат выражения будет равен false, цикл for прервется.
increment – выражение, которое исполняется после каждой итерации цикла. Чаще всего в данном выражении происходит инкрементирование или декрементирование переменной счетчика.
statement(s) – тело цикла.
Выражения initialization, termination, increment опциональны. Если опустить каждое из них, мы получим бесконечный цикл:
// бесконечный цикл
for ( ; ; ) {
// код тела цикла
}
Этот Java цикл выглядит так:
for (initialization; termination; increment) {
statement(s)
}
Здесь:
initialization – выражение, которое инициализирует выполнение цикла. Исполняется только раз в начале цикла. Чаще всего в данном выражении инициализируют счетчик цикла.
termination – boolean выражение, которое регулирует окончание выполнения цикла. Если результат выражения будет равен false, цикл for прервется.
increment – выражение, которое исполняется после каждой итерации цикла. Чаще всего в данном выражении происходит инкрементирование или декрементирование переменной счетчика.
statement(s) – тело цикла.
Выражения initialization, termination, increment опциональны. Если опустить каждое из них, мы получим бесконечный цикл:
// бесконечный цикл
for ( ; ; ) {
// код тела цикла
}
👍5⚡1❤1✍1🔥1👨💻1
Цикл for each в Java.
Этот цикл Java – разновидность цикла for для итерации коллекций и массивов.
Структура for each выглядит так:
for (String var : listOfStrings) {
statement(s)
}
Здесь:
listOfStrings – переменная, на которую ссылается существующий список или массив.
String var – определение новой переменной того же типа (String), что и коллекция listOfStrings.
statement(s) – тело цикла.
Данную конструкцию можно прочитать так: “Для каждого var из listOfStrings сделать...”.
Предположим, у нас есть массив строк из названий дней недели (на второй картинке). Теперь попробуйте предположить, что будет выведено после исполнения программы.
Этот цикл Java – разновидность цикла for для итерации коллекций и массивов.
Структура for each выглядит так:
for (String var : listOfStrings) {
statement(s)
}
Здесь:
listOfStrings – переменная, на которую ссылается существующий список или массив.
String var – определение новой переменной того же типа (String), что и коллекция listOfStrings.
statement(s) – тело цикла.
Данную конструкцию можно прочитать так: “Для каждого var из listOfStrings сделать...”.
Предположим, у нас есть массив строк из названий дней недели (на второй картинке). Теперь попробуйте предположить, что будет выведено после исполнения программы.
👍6🔥2⚡1✍1👨💻1