В книге "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 -3 | jstack |
---|---|
|
|
|
|
|
|
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
Вот несколько примеров:
Команда | Описание |
---|---|
| Подробная информация обо всех загруженных классах |
| Гистограмма количества экземпляров |
| Создать дамп кучи. |
| Обзор использования кучи |
| Запустить сборку мусора |
| Дамп потока |
| Начать запись JDK JFR |
| Создать дамп JFR |
| Время выполнения JVM |
| Активные флаги JVM |
| Свойства системы |
| Командная строка JVM |
| JVM-версия |
| Визуальный вывод иерархии классов |
| Управление ведением журнала 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, могут облегчить вашу жизнь, если вы знаете об их возможностях и о том, как их комбинировать друг с другом и с другими инструментами оболочки.
Их определенно стоит попробовать и узнать о них больше.