Ручная кофемолка: инструменты командной строки для Java

Kate

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

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

Я предпочитаю командную строку для своей повседневной работы, используя комбинацию команд git, sed, grep, и т. д., 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 -3jstack
RUNNABLEIN_NATIVE
TIMED_WAITINGBLOCKED
WAITINGBLOCKED (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​

Всякий раз, когда вы хотите изучить результат javac, javap вступает в игру.
Этот инструмент позволяет отображать сигнатуру класса, его расположение в памяти с помощью флагов -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.versionJVM-версия
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.

c66c82ea0b2cfb43c2bdf9552ceadaf1.png

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, могут облегчить вашу жизнь, если вы знаете об их возможностях и о том, как их комбинировать друг с другом и с другими инструментами оболочки.

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

 
Сверху