Статьи

JVM без сборки мусора

Сообщество JVM продолжает добавлять новый GC, и недавно был добавлен новый, который называется Epsilon и является особенным. Epsilon только выделяет память, но не восстанавливает ее.

Это может выглядеть как использование GC, который не выполняет сборку мусора. Этот тип сборщика мусора имеет специальное применение, и мы рассмотрим некоторые из них.

Где этот шинный GC можно использовать?

Тестирование производительности

Если вы разрабатываете решение с жестким требованием к времени ожидания и ограниченным бюджетом памяти, этот GC можно использовать для тестирования предела программы.

Тестирование давления памяти

Хотите знать, как извлечь временное требование к памяти для вашего приложения. Я считаю это полезным, если вы создаете какое-то чистое решение в памяти.

Алгоритм разметки стенда.

Много раз мы хотели проверить реальную производительность нового классного алгоритма, основываясь на нашем понимании понятия BIG (O), но сборщик мусора добавляет шум во время тестирования.

Низкий мусор

Много раз мы проводим некоторую оптимизацию в алгоритме, чтобы уменьшить количество производимого мусора, и GC, такой как epsilon, помогает в научной проверке оптимизации.

Как включить epsilon GC

Инженеры JVM позаботились о том, чтобы этот GC не включался по умолчанию в производстве, поэтому для использования этого GC мы должны использовать параметры ниже JVM

-XX: + UnlockExperimentalVMOptions -XX: + UseEpsilonGC -Xlog: gc

Один вопрос, который может возникнуть у вас в голове, что происходит, когда память исчерпана? JVM остановится с ошибкой OutofMemory.

Давайте посмотрим на код для тестирования GC

Как узнать, используется ли epsilon в процессе JVM?

Java имеет хороший API-интерфейс управления, который позволяет запрашивать текущий используемый GC, это также можно использовать для проверки того, какой GC по умолчанию используется в другой версии Java.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
          public class VerifyCurrentGC {
  
public static void main(String... args) {
  
var gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
  
gcBeans.stream().forEach(gc -> {
  
out.println(format("GC Name : %s", gc.getName()));
var poolNames = gc.getMemoryPoolNames();
if (poolNames != null) {
List.of(poolNames).forEach(pool ->
out.println(format("Pool name %s", pool)));
} else {
out.println("No memory pools for " + gc.getName());
}
  
});
  
}
}

Запустите код выше с параметрами ниже

-XX: + UnlockExperimentalVMOptions -XX: + UseEpsilonGC VerifyCurrentGC

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

Я буду использовать код ниже, чтобы показать, как работает новый GC.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
          public class MemoryAllocator {
  
public static final int KB = 1024;
static int mbToAllocate = Integer.getInteger("mb", 1000);
  
public static void main(String[] args) {
System.out.println(String.format("Start allocation of %s MBs", mbToAllocate));
  
for (var i = 0; i < mbToAllocate; i++) {
var garbage = new byte[KB * KB];
}
  
System.out.println("I was Alive after allocation");
}
}

Выполнение кода выше с GC по умолчанию и запрос выделения 5 ГБ не вызывает проблем ( java -Xlog: gc -Dmb = 5024 MemoryAllocator ) и выдает результат ниже

[0.016s] [info] [gc] Использование G1
[0.041s] [info] [gc] Периодический сбор данных отключен
Начать выделение 5024 МБ
[0.197s] [info] [gc] GC (0) Пауза Янга (одновременный запуск) (G1 Humongous Allocation) 116M-> 0M (254M) 3.286ms
[0.197s] [info] [gc] GC (1) Параллельный цикл
[0.203s] [info] [gc] GC (1) Пауза Примечание 20M-> 20M (70M) 4.387ms
[0.203s] [info] [gc] GC (1) Очистка паузы 22M-> 22M (70M) 0,043 мс
[1.600s] [info] [gc] GC (397) Параллельный цикл 6.612ms
[1.601s] [info] [gc] GC (398) Пауза Янга (одновременный запуск) (G1 Humongous Allocation) 52M-> 0M (117M) 1.073ms
[1.601s] [info] [gc] GC (399) Параллельный цикл
Я был жив после распределения
[1.606s] [info] [gc] GC (399) Пауза Примечание 35M-> 35M (117M) 0,382 мс

[1.607s] [info] [gc] GC (399) Очистка паузы 35M-> 35M (117M) 0,093 мс
[1.607s] [info] [gc] GC (399) Параллельный цикл 6.062ms

Добавим некоторое ограничение памяти ( java -XX: + UnlockExperimentalVMOptions -XX: + UseEpsilonGC -Xlog: gc -Xmx1g -Dmb = 5024
MemoryAllocator)
[0.011s] [info] [gc] Изменяемая куча; начиная с 253M, максимум: 1024M, шаг: 128M
[0.011s] [info] [gc] Использование выделения TLAB; максимум: 4096К
[0.011s] [info] [gc] Включены эластичные TLAB; эластичность: 1.10x
[0.011s] [info] [gc] Включен распад эластичных TLAB; время затухания: 1000 мс
[0.011s] [info] [gc] Использование Epsilon
Начать выделение 5024 МБ
[0.147s] [info] [gc] Куча: зарезервировано 1024M, зафиксировано 253M (24.77%), использовано 52640K (5.02%)
[0.171s] [info] [gc] Куча: зарезервировано 1024M, зафиксировано 253M (24.77%), использовано 103M (10.10%)
[0.579s] [info] [gc] Куча: зарезервировано 1024M, зафиксировано 1021M (99.77%), использовано 935M (91.35%)
[0.605s] [info] [gc] Куча: зарезервировано 1024M, зафиксировано 1021M (99.77%), использовано 987M (96.43%)

Завершение из-за java.lang.OutOfMemoryError: пространство кучи Java

Этот конкретный запуск вызвал ошибку OOM и является хорошим подтверждением того, что после 1 ГБ эта программа потерпит крах.

Такое же поведение является истинной многопоточной программой, см. Образец в MultiThreadMemoryAllocator.java .

Модульные тесты доступны для тестирования функций этого специального ГХ.

Я думаю, что Epsilon найдет больше вариантов использования и внедрения в будущем, и это, безусловно, хороший шаг для расширения охвата JVM.

Все примеры кода доступны Github репо

Если вам понравился пост, вы можете подписаться на меня в твиттере.

Смотреть оригинальную статью здесь: JVM без сборки мусора

Мнения, высказанные участниками Java Code Geeks, являются их собственными.