В книге "97 вещей, которые должен знать каждый Java-программист" есть глава о некоторых инструментах командной строки в JDK (я дал 2 из 97 советов).

Поскольку я сам часто использую такие помощники, я хотел кратко представить их в сегодняшней статье.

Я предпочитаю командную строку для своей повседневной работы, используя комбинацию команд gitsedgrep, и т. д., bash что упрощает выполнение повторяющихся задач.

Уже в «Прагматическом программисте» была четкая ссылка на это в разделе 21:

Используйте возможности командных оболочек+
Используйте оболочку, когда графические пользовательские интерфейсы не подходят.

Также хотелось бы сослаться на книгу NealFord "Productive Programmer":

Вам не нужно бояться командной строки, это как любой язык программирования. Вы выполняете команды или сценарии и можете комбинировать их ввод и вывод в более сложные процессы. В Linux и OSX вас хорошо обслуживают встроенные bash или zsh, в Windows вы можете воспользоваться либо cmd, либо с недавних пор с подсистемой Windows для Linux (WSL).

Большинство инструментов поставляются со встроенной справкой, отображаемой по параметру -h или предоставляют страницы справки с помощью `man`. Эти справочные страницы также можно найти на веб-сайте Oracle.

Управление установками JDK с помощью SDKman

Как упоминалось в предыдущих статьях, для меня sdkman — это гений управления установками Java, Groovy, Maven, Gradle, Micronaut и многих других инструментов, а также для активации различных версий.

Для этого вы устанавливаете sdkman, например, с помощью: 

curl -s "https://get.sdkman.io" | bash.

После этого по команде sdk list отображаются устанавливаемые инструменты, и с помощью команды sdk list java вы можете увидеть доступные и установленные версии JDK.
У вас есть широкий выбор версий JDK от OpenJDK до Azul Zuulu, GraalVM до Amazon и SAP JDK.

С помощью, например sdk install java 17-open, вы можете установить новые версии (вплоть до последней EAP), а с помощью sdk use java 17-open вы можете переключиться на текущую оболочку или глобально.

Простые помощники

В каждом пакете JRE и JDK помимо компилятора javac и среды выполнения java есть много полезных помощников в каталоге bin дистрибутива. Некоторые из них, как jarsigner или keytool очень специфичные, и я не буду углубляться в них здесь. 

Мы начнем с некоторых полезных инструментов, а затем посвятим более сложным инструментам отдельные разделы.

jps

Если вы хотите избавиться от зависшего процесса Java, вы можете либо найти его в диспетчере задач и закрыть его там, либо найти PID (идентификатор процесса) с помощью, ps auxww | grep java, а затем завершить его с помощью `kill`.

Вместо этого встроенный jps может предоставить тот же сервис.
Есть дополнительные параметры: -l - для полного доменного имени основного класса или пути к JAR-файлу запуска, -v - для аргументов JVM и -m - для аргументов командной строки метода main.

jstack

Чтобы получить дамп потока JVM, особенно если он завис в какой-то точке, которую вы хотите изучить более внимательно, есть 2 способа.
Либо `kill -3` производит вывод непосредственно из процесса, либо предпочтительнее с помощью `jstack`.

Таким образом, jstack также может заставить остановившиеся процессы выводить с флагом «force» -F, который затем можно перенаправить в файл.
С помощью `jstack -l` вы получаете дополнительный вывод о блокировках, также отображаются взаимоблокировки.

Статус потоков различается между kill -3и jstack.

kill -3

jstack

RUNNABLE

IN_NATIVE

TIMED_WAITING

BLOCKED

WAITING

BLOCKED (PARK)

jinfo

С помощью jinfo вы можете быстро получить доступ к системным свойствам, флагам JVM и аргументам JVM процесса Java.

jinfo дает полный обзор, который может помочь в обнаружении странных эффектов. Используя jinfo -flag name=value или -flag [+|-]name вы можете изменить динамические флаги JVM.

jshell

Представленная в Java 9, jshell - это была первая официальная консоль REPL (read eval print loop) для интерактивного выполнения кода Java.
Можно не только вычислять выражения и назначать переменные, но также можно создавать и динамически переопределять классы с методами.

Можно передать пути к классам в jshell, содержимое которого затем будет доступно для импорта и использования.

jshell имеет множество параметров командной строки, а также встроенные команды, которые объясняются с помощью параметра /?.
Особенно полезны /help/save/history и команды /vars, /types, /methods, /imports.

Для редактирования больших фрагментов кода вы можете использовать '/edit', создав свой собственный редактор, используя переменные окружения 'JSHELLEDITOR', 'VISUAL', 'EDITOR' или установить редактор указав '/path/to/editor'.

Важные пакеты java.util.(*,streams,concurrent), такие как java.math и некоторые другие, уже импортированы по умолчанию.

Выражения присваиваются заполнителям $5, которые можно использовать позже.
Для улучшения читабельности кода лучше использовать var начиная с Java 11, тогда переменные можно создавать без объявления типа.
Новые языковые функции, которые все еще доступны в режиме предварительного просмотра, могут быть включены с помощью параметра --enable-preview.

Очень удобной функцией jshell является автодополнение. Каждое имя класса, метода и переменной можно контекстно завершить, нажав несколько раз клавишу Tab.

Вот пример запуска игры «Жизнь» (Покойся с миром — Джон Конвей) в jshell.

// GOL Rules: Cell is alive, if it was alive and has 2 or 3 living neighbours or always with 3 living neighbours
import static java.util.stream.IntStream.range;
import static java.util.stream.Collectors.*;
import static java.util.function.Predicate.*;

record Cell(int x, int y) {
   Stream nb() {
       return range(x()-1,x()+2)
         .mapToObj(i -> i)
         .flatMap(x -> range(y()-1,y()+2)
         .mapToObj(y -> new Cell(x,y)))
         .filter(c -> !this.equals(c));
   }
   boolean alive(Set cells) {
       var count = nb().filter(cells::contains).count();
       return (cells.contains(this) && count == 2) || count == 3;
   }
}
Set evolve(Set cells) {
    return cells.stream().flatMap(c -> c.nb()).distinct()
    .filter(c -> c.alive(cells))
    .collect(toSet());
}
void print(Set cells) {
    var min=new Cell(cells.stream().mapToInt(Cell::x).min().getAsInt(),
                     cells.stream().mapToInt(Cell::y).min().getAsInt());
    var max=new Cell(cells.stream().mapToInt(Cell::x).max().getAsInt(),
                     cells.stream().mapToInt(Cell::y).max().getAsInt());

    range(min.y(), max.y()+1)
    .mapToObj(y -> range(min.x(), max.x()+1)
    .mapToObj(x -> cells.contains(new Cell(x,y)) ? "X" : " ")
    .collect(joining(""))).forEach(System.out::println);
}
"""
 #
  #
###
"""
var cells = Set.of(new Cell(1,0), new Cell(2,1), new Cell(0,2),new Cell(1,2),new Cell(2,2))

void gen(Set cells, int steps) {
    print(cells);
    if (steps>0) gen(evolve(cells),steps-1);
}

Set parse(String s) {
    Arrays.stream(s.split("\n")).mapIndexed((x,l) ->
    Arrays.stream(l.split("")).mapIndexed(y,c) -> )
}

jar

Для работы с jar-файлами (Java ARchive) есть одноименная команда.
Синтаксис командной строки аналогичен команде tar.
Хотя tar по умолчанию только хранит файлы в архиве, jar также сжимает их, что приводит к значительному уменьшению размера.

Вот несколько полезных сценариев использования:

  • jar tf file.jar - отображать содержимое архива

  • jar xvf file.jar - распаковать файл архива в текущем каталоге (с отображением по v)

  • jar uvf file.jar -C path test.txt - добавить файл из указанной директории

Поскольку в Java 9 jar также может создавать мультирелизные архивы, совместимые с несколькими JDK и могут содержать оптимизированные файлы классов для соответствующей версии Java.

java

Команда Java запускает виртуальную машину Java с заданным путем к классам (каталоги, файлы и URL-адреса jar и классов) и основным классом, main метод которого выполняется.

С помощью команды java -jar file.jar main класс определяется с помощью метаинформации файла jar.

Начиная с Java 11, доступен JEP 330 (Запуск программ с однофайловым исходным кодом), поэтому исходные файлы можно запускать напрямую.

cat > Hello.java < hello <<EOF
#!/usr/bin/java --source 10
public class Hello {
    public static void main(String...args) {
        System.out.println("Hello "+String.join(" ",args)+"!");
    }
}
EOF
chmod +x hello
./hello JEP 330

JVM можно контролировать с помощью сотен флагов, от выделения памяти с помощью флагов -Xmx и -Xms до выбора сборщика мусора с помощью флага -XG1GC и настроек журнала.
Коллекция ресурсов по флагам JVM была опубликована Betsy Rhodes на Foojay

Далее следуют несколько полезных флагов, список представляет лишь часть опций JVM.

  • HeapDumpOnOutOfMemoryError

  • Xshareclasses- Обмен данными класса

  • verbose:gc- Ведение журнала GC

  • +TraceClassLoading

  • +UseCompressedStrings

Javac

Компилятор javac транслирует исходный код Java в один или несколько файлов классов, содержащих байт-код классов, выполняет начальную оптимизацию и запускает обработку аннотаций «процессорами аннотаций».
Чтобы указать все классы, от которых зависит текущий код, они или их архивы должны быть перечислены в пути к классам или пути к модулю.

Для более глубокого изучения javac потребуется отдельная статья, поэтому мы остановимся на ее почетном упоминании.

JavaP

Всякий раз, когда вы хотите изучить результат javacjavap вступает в игру.
Этот инструмент позволяет отображать сигнатуру класса, его расположение в памяти с помощью флагов -l -v -constants или инструкциями байт-кода языка стека JVM с помощью флага-c.
Это может быть полезно, если вы хотите увидеть влияние определенных опций компилятора или версий Java, или если изменилось поведение оптимизаций (размер встроенного кода).

В качестве параметра он получает полное имя класса, имя файла или URL-адрес jar.

Вот пример нашего класса Hello.java, где вы можете видеть, например, что Java 14 теперь использует операцию «invokedynamic» для конкатенации строк.

javap -c Hello

Compiled from "Hello.java"
public class Hello {
  // Constructor with Super-Constructor call
  public Hello();
    Code:
       // load "this" on stack
       0: aload_0
       4: return

  public static void main(java.lang.String...);
    Code:
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #13                 // String
       // load first parameter on stack, i.e. "args"
       5: aload_0
       6: invokestatic  #15                 // Method java/lang/String.join:(Ljava/lang/CharSequence;[Ljava/lang/CharSequence;)Ljava/lang/String;
       // string concatenation
       9: invokedynamic #21,  0             // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
      14: invokevirtual #25                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      17: return
}

JMAP

Было полезно создавать heapdumps или гистограммы (ссылочных) объектов jmap.
В настоящее время рекомендуется использовать jcmd.

  • jmap -clstats выводит статистику загрузчика классов

  • jmap -histo или jmap -histo:live выводит гистограмму

  • jmap -dump:live,format=b,file=heap.hprof генерирует дамп кучи.

JCMD

Используя jcmd можно управлять Java процессами удаленно, существует довольно много действий, которые можно инициировать в JVM.
jcmd может использоваться интерактивно или с помощью параметров командной строки.

Используя jcmd, можно запускать определенные действия, при этом несколько команд разделяются символами новой строки.
Кроме того jcmd help дает информацию о том, какие команды имеются.

jcmd 14358

Вот несколько примеров:

Команда

Описание

GC.class_stats

Подробная информация обо всех загруженных классах

GC.class_histogram

Гистограмма количества экземпляров

GC.heap_dump filename=

Создать дамп кучи.

GC.heap_info

Обзор использования кучи

GC.run

Запустить сборку мусора

Thread.print

Дамп потока

JFR.start name= settings= delay=20s duration=2m

Начать запись JDK JFR

JFR.dump name= filename=

Создать дамп JFR

VM.uptime

Время выполнения JVM

VM.flags

Активные флаги JVM

VM.system_properties

Свойства системы

VM.command_line

Командная строка JVM

VM.version

JVM-версия

VM.class_hierarchy

Визуальный вывод иерархии классов

VM.log

Управление ведением журнала JVM

jcmd 15254 GC.heap_info
15254:
 garbage-first heap   total 1048576K, used 214334K [0x00000007c0000000, 0x0000000800000000)
  region size 1024K, 135 young (138240K), 0 survivors (0K)
 Metaspace       used 136764K, capacity 142605K, committed 142896K, reserved 1169408K
  class space    used 19855K, capacity 22505K, committed 22576K, reserved 1048576K
jcmd GradleDaemon GC.class_histogram | head
14358:

 num     #instances         #bytes  class name
----------------------------------------------
   1:         42635        4515304  [C
   2:         10100        1096152  java.lang.Class
   3:         42595        1022280  java.lang.String
   4:         27743         887776  java.util.concurrent.ConcurrentHashMap$Node
   5:         10598         599128  [Ljava.lang.Object;
   6:         26119         417904  java.lang.Object

JDK Flight Recorder (jfr)

JDK Flight Recorder — это механизм для трассировки во время выполнения, который позволяет записывать различные события активности, происходящие в JVM, и соотносить их с активностью приложения.
Возможна трассировка всего, включая JIT-оптимизации, сборки мусора, точек сохранения и даже пользовательских событий.

Инструмент jfr позволяет читать и отображать файлы JDK Flight Recorder с помощью команд print, summary и metadata.
Выдачу результатов можно представить в удобочитаемом текстовом формате или JSON/XML ( --json, --xml).

  • print представляет весь журнал событий

  • metadata показывает, какие события были записаны (классы событий)

  • summary показывает в виде гистограммы, какие события были записаны, как часто

jfr summary /tmp/test.jfr

 Version: 2.0
 Chunks: 1
 Start: 2020-06-21 12:06:38 (UTC)
 Duration: 7 s

 Event Type                            Count  Size (bytes)
===========================================================
 jdk.ModuleExport                       2536         37850
 jdk.ClassLoaderStatistics              1198         35746
 jdk.NativeLibrary                       506         45404
 jdk.SystemProcess                       490         53485
 jdk.JavaMonitorWait                     312          8736
 jdk.NativeMethodSample                  273          4095
 jdk.ModuleRequire                       184          2578
 jdk.ThreadAllocationStatistics           96          1462
 jdk.ThreadSleep                          65          1237
 jdk.ThreadPark                           53          2012
 jdk.InitialEnvironmentVariable           40          2432
 jdk.InitialSystemProperty                20         16392
 jdk.ThreadCPULoad                        17           357

Чтобы ограничить количество информации, категории можно фильтровать с помощью флага --categories "GC,JVM,Java*", а события с помощью флага --events CPULoad,GarbageCollection или --events "jdk.*".
К сожалению, это невозможно с помощью summary или metadata, только с помощью print.

Лучшим инструментом для оценки записей JFR, конечно же, является JDK Mission Control (JMC), который был выпущен как OpenSource, начиная с Java 11, а также предлагается другими поставщиками, такими как Azul.

jdeprscan

Поскольку в последние годы некоторые компоненты были исключены из JDK (discontinued), jdeprscan позволяет сканировать классы, каталоги или файлы jar для определения использования этих API.

Пример:

jdeprscan --release 11 testcontainers/testcontainers/1.9.1/testcontainers-1.9.1.jar 2>&1 | grep -v 'error: cannot '
Jar file testcontainers/testcontainers/1.9.1/testcontainers-1.9.1.jar:
class org/testcontainers/shaded/org/apache/commons/lang/reflect/FieldUtils uses
  deprecated method java/lang/reflect/AccessibleObject::isAccessible()Z
class org/testcontainers/shaded/org/apache/commons/lang/reflect/MemberUtils uses
  deprecated method java/lang/reflect/AccessibleObject::isAccessible()Z
class org/testcontainers/shaded/org/apache/commons/io/input/ClassLoaderObjectInputStream
  uses deprecated method java/lang/reflect/Proxy::getProxyClass(Ljava/lang/Class

С помощью jdeprscan --list --release 11 вы можете перечислить API, которые объявлены устаревшими (deprecated) в этом релизе.

jdeprscan --release 11 --list | cut -d' ' -f 3- | cut -d. -f1-3 | sort | uniq -c | sort -nr | head -10
 132
  40 java.rmi.server
  34 java.awt.Component
  25 javax.swing.text
  25 javax.swing.plaf
  20 javax.management.monitor
  18 java.util.Date
  13 java.awt.List
   9 javax.swing.JComponent
   8 java.util.concurrent

Другие инструменты

Есть, конечно, еще много важных инструментов для работы с JVM, от async-profiler и jol (Java Object Layout) до графических программ для разбора и отображения журналов GC (https://gceasy.io), записей JFR (jmc) или дампов кучи (jvisualvm, Eclipse-MAT).

Другие инструменты, такие как отладчик Java jdb, не так удобны, как возможности IDE для удобной отладки как на локальных, так и на удаленных компьютерах.

Заключение

Помощники, поставляемые с JDK, могут облегчить вашу жизнь, если вы знаете об их возможностях и о том, как их комбинировать друг с другом и с другими инструментами оболочки.

Их определенно стоит попробовать и узнать о них больше.

Комментарии (1)


  1. mgubaidullin
    24.02.2022 11:04

    Если говорить про java в командной строке, не забудьте попробовать jbang

    https://jbang.dev