1. Обзор
В дополнение к типичным утилитам разработки, таким как компилятор и среда выполнения, каждый выпуск JDK поставляется с множеством других инструментов. Некоторые из этих инструментов могут помочь нам получить ценную информацию о наших запущенных приложениях.
В этой статье мы увидим, как мы можем использовать такие инструменты, чтобы узнать больше об алгоритме GC, используемом конкретным экземпляром JVM.
2. Образец заявления
В этой статье мы будем использовать очень простое приложение:
public class App {
public static void main(String[] args) throws IOException {
System.out.println("Waiting for stdin");
int read = System.in.read();
System.out.println("I'm done: " + read);
}
}
Очевидно, это приложение ждет и продолжает работать, пока не получит что-то из стандартного ввода. Эта приостановка помогает нам имитировать поведение долго работающих приложений JVM.
Чтобы использовать это приложение, мы должны скомпилировать файл App.java с помощью
javac
, а затем запустить его с помощью инструмента java .
3. Поиск процесса JVM
Чтобы найти GC, используемый процессом JVM, сначала мы должны определить идентификатор процесса этого конкретного экземпляра JVM. Допустим, мы запустили наше приложение с помощью следующей команды:
>> java App
Waiting for stdin
Если у нас установлен JDK, лучший способ найти идентификатор процесса экземпляров JVM — использовать инструмент jps
. Например:
>> jps -l
69569
48347 App
48351 jdk.jcmd/sun.tools.jps.Jps
Как показано выше, в системе запущено три экземпляра JVM. Очевидно, что описание второго экземпляра JVM («Приложение») совпадает с названием нашего приложения. Следовательно, идентификатор процесса, который мы ищем, — 48347.
В дополнение к jps
мы всегда можем использовать другие общие утилиты для фильтрации запущенных процессов. Например, известный инструмент ps
из пакета procps также будет работать:
>> ps -ef | grep java
502 48347 36213 0 1:28AM ttys037 0:00.28 java App
Однако jps
намного проще в использовании и требует меньше фильтрации.
4. Используемый ГХ
Теперь, когда мы знаем, как найти идентификатор процесса, давайте найдем алгоритм GC, используемый уже запущенными приложениями JVM.
4.1. Java 8 и более ранние версии
Если мы используем Java 8, мы можем использовать утилиту jmap
для печати сводки кучи, гистограммы кучи или даже создания дампа кучи . Чтобы найти алгоритм GC, мы можем использовать параметр -heap
как:
>> jmap -heap <pid>
Итак, в нашем конкретном случае мы используем CMS GC:
>> jmap -heap 48347 | grep GC
Concurrent Mark-Sweep GC
Для других алгоритмов GC вывод почти такой же:
>> jmap -heap 48347 | grep GC
Parallel GC with 8 thread(s)
4.2. Java 9+: jhsdb jmap
Начиная с Java 9, мы можем использовать комбинацию jhsdb jmap
для вывода некоторой информации о куче JVM. В частности, эта конкретная команда будет эквивалентна предыдущей:
>> jhsdb jmap --heap --pid <pid>
Например, наше приложение сейчас работает с G1GC:
>> jhsdb jmap --heap --pid 48347 | grep GC
Garbage-First (G1) GC with 8 thread(s)
4.3. Java 9+: jcmd
В современных JVM команда jcmd
довольно универсальна. Например, мы можем использовать его для получения общей информации о куче :
>> jcmd <pid> VM.info
Итак, если мы передадим идентификатор процесса нашего приложения, мы увидим, что этот экземпляр JVM использует Serial GC:
>> jcmd 48347 VM.info | grep gc
# Java VM: OpenJDK 64-Bit Server VM (15+36-1562, mixed mode, sharing, tiered, compressed oops, serial gc, bsd-amd64)
// omitted
Вывод аналогичен для G1 или ZGC:
// ZGC
# Java VM: OpenJDK 64-Bit Server VM (15+36-1562, mixed mode, sharing, tiered, z gc, bsd-amd64)
// G1GC
# Java VM: OpenJDK 64-Bit Server VM (15+36-1562, mixed mode, sharing, tiered, compressed oops, g1 gc, bsd-amd64)
Применив немного магии grep
, мы также можем удалить все эти шумы и просто получить имя сборщика мусора:
>> jcmd 48347 VM.info | grep -ohE "[^\s^,]+\sgc"
g1 gc
4.4. Аргументы командной строки
Иногда мы (или кто-то другой) явно указываем алгоритм GC при запуске приложения JVM. Например, здесь мы решили использовать ZGC:
>> java -XX:+UseZGC App
В таких случаях есть гораздо более простые способы найти используемый GC. По сути, все, что нам нужно сделать, это каким-то образом найти команду, с которой было выполнено приложение .
Например, на платформах на базе UNIX мы можем снова использовать команду ps :
>> ps -p 48347 -o command=
java -XX:+UseZGC App
Из приведенного выше вывода очевидно, что JVM использует ZGC. Точно так же команда jcmd
также может распечатать аргументы командной строки :
>> jcmd 48347 VM.flags
84020:
-XX:CICompilerCount=4 -XX:-UseCompressedOops -XX:-UseNUMA -XX:-UseNUMAInterleaving -XX:+UseZGC // omitted
Удивительно, как показано выше, эта команда будет печатать как неявные, так и явные аргументы и настраиваемые параметры . Таким образом, даже если мы не укажем алгоритм GC явно, он покажет выбранный и используемый по умолчанию:
>> jcmd 48347 VM.flags | grep -ohE '\S*GC\s'
-XX:+UseG1GC
И что еще более удивительно, это будет работать и на Java 8:
>> jcmd 48347 VM.flags | grep -ohE '\S*GC\s'
-XX:+UseParallelGC
5. Вывод
В этой статье мы рассмотрели различные подходы к поиску алгоритма GC, используемого конкретным экземпляром JVM. Некоторые из упомянутых подходов были привязаны к конкретным версиям Java, а некоторые были переносимыми.
Более того, мы увидели пару способов найти id процесса, который нужен всегда.