Guide contents

Lesson 14 of 14

JDK Command-Line Tools

The JDK ships with the tools needed to compile, package, run, and diagnose a Java program without an IDE. This lesson covers the tools in the order a program moves through them: build, run, then inspect a running JVM. All output below comes from JDK 25 on Linux.

Tool Purpose
java Start a JVM, run a class, a jar, or a single source file
javac Compile .java files to .class files
jar Create, list, and extract jar archives
jshell Evaluate Java snippets interactively or from a script
jps List running JVM processes
jcmd Send a diagnostic command to a running JVM
jstack Print the thread stacks of a running JVM
jmap Print a class histogram of the heap
jstat Print garbage collector and memory statistics over time
jfr Read and summarize Flight Recorder files
JAVA_TOOL_OPTIONS Environment variable that adds JVM options to every java start

The diagnostic tools (jps, jcmd, jstack, jmap, jstat) attach to a JVM owned by the same operating system user. Process ids and timestamps differ on every run, so the outputs below replace process ids with PID where a command prints one.

Example 1: java -version

The first check on any machine is which JVM java starts. The command prints the version and the build.

Terminal
java -version
java --version
Output
openjdk version "25.0.4.1" 2026-08-18
OpenJDK Runtime Environment (build 25.0.4.1+1-1-24.04.4-Ubuntu)
OpenJDK 64-Bit Server VM (build 25.0.4.1+1-1-24.04.4-Ubuntu, mixed mode, sharing)
openjdk 25.0.4.1 2026-08-18
OpenJDK Runtime Environment (build 25.0.4.1+1-1-24.04.4-Ubuntu)
OpenJDK 64-Bit Server VM (build 25.0.4.1+1-1-24.04.4-Ubuntu, mixed mode, sharing)

The first form is the older single-dash option and writes to standard error. The second form is the double-dash option and writes to standard output, so it can be piped or captured with $(...). The first line names the vendor build and the release. The last line names the VM and its mode: mixed mode means the JVM both interprets and compiles code, and sharing means class data sharing is active, which shortens startup.

If several JDKs are installed, JAVA_HOME and the PATH order decide which one runs. The tools in this lesson all come from the same bin directory, so a jcmd from one JDK may fail to attach to a JVM started by another. Matching versions avoids that.

Example 2: java single-file source launch

Since JDK 11, java can compile and run one source file in memory, with no separate javac step. This suits small scripts and examples.

Java
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello from " + (args.length > 0 ? args[0] : "nobody"));
    }
}
Terminal
java Hello.java world
Output
Hello from world

The argument ends in .java, so java compiles the file in memory and runs the first class declared in it. Everything after the file name goes to the program as args. No .class file is written to disk.

The mode has limits. It handles one file only, so a program split across several source files needs javac. Options for the compiler and the JVM go before the file name, as in java -Xmx64m Hello.java.

Example 3: javac -d

A project with a package needs a compile step that writes class files into a directory tree. The -d option names that directory.

Java
package com.example;

public class Greeter {
    public String greet(String name) {
        return "hello, " + name;
    }
}
Java
package com.example;

public class App {
    public static void main(String[] args) {
        System.out.println("App " + new Greeter().greet("jar"));
    }
}

These two files live in src/com/example/. The App block depends on Greeter in another file, so the example checker skips it. Compile them into out and run the result:

Terminal
javac -d out src/com/example/*.java
find out -type f | sort
java -cp out com.example.App
Output
out/com/example/App.class
out/com/example/Greeter.class
App hello, jar
Option Meaning
-d out Write class files under out, creating one directory per package segment
-cp out Class path: directories and jars where java looks for classes
--release 21 Compile against the Java 21 API and language level, whatever JDK runs javac

The -d directory is created if it is missing. The directory layout mirrors the package name, which is why com.example.App becomes out/com/example/App.class. java then takes the fully qualified class name, not a file path.

Example 4: jar create and list

A jar is a zip file with a manifest. It packages a class tree into one file that java -jar can start.

Terminal
jar --create --file app.jar --main-class com.example.App -C out .
jar --list --file app.jar
java -jar app.jar
Output
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/App.class
com/example/Greeter.class
App hello, jar
Option Meaning
--create (-c) Create a new archive
--file (-f) Name of the archive
--main-class (-e) Write the entry point into the manifest, so java -jar knows which class to start
-C out . Change into out and add everything under it, so entries start at com/ and not at out/com/
--list (-t) List the entries
--extract (-x) Extract the entries into the current directory

The tool added META-INF/MANIFEST.MF on its own. The short form jar tf app.jar lists the same entries, and jar cfe app.jar com.example.App -C out . is the short form of the create command. A common mistake is running jar from the parent directory without -C. The entries then begin with out/, and java -jar fails to find com.example.App.

Example 5: jshell

jshell evaluates Java statements and expressions one at a time, with no class or main method. It is a read-eval-print loop (REPL), a program that reads one input, runs it, and prints the result.

Terminal
cat > cmds.jsh <<'EOF2'
int x = 6 * 7;
String s = "jdk".toUpperCase();
System.out.println(x + " " + s);
/exit
EOF2
jshell -s cmds.jsh
Output
42 JDK

The file mixes Java snippets with jshell commands. Lines that start with / are jshell commands: /exit ends the session, /vars lists variables, /imports lists imports, and /help lists the rest. The -s option selects the concise feedback mode, so assignments do not print a line such as x ==> 42 for each snippet. Without a file argument, jshell opens an interactive prompt that reads from the terminal.

jshell imports java.util, java.io, java.math, java.util.stream, and a few other packages by default, so snippets like List.of(1, 2, 3) need no import line. The tool suits checking an API call or a format string before putting it in a source file.

Target program for Examples 6 to 11

The next six tools inspect a running JVM, so they need a process to inspect. This program starts one daemon thread named worker-1 that allocates arrays in a loop and sleeps briefly, then keeps the JVM alive for ten minutes.

Java
import java.util.ArrayList;
import java.util.List;

public class Target {
    static final List<byte[]> KEEP = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        Thread worker = new Thread(() -> {
            long n = 0;
            while (true) {
                KEEP.add(new byte[10_000]);
                if (KEEP.size() > 2000) KEEP.clear();
                n++;
                if (n % 200 == 0) {
                    try { Thread.sleep(5); } catch (InterruptedException e) { return; }
                }
            }
        }, "worker-1");
        worker.setDaemon(true);
        worker.start();
        Thread.sleep(600_000);
    }
}

Compile it, start it in the background, and keep its process id in a shell variable:

Terminal
javac Target.java -d tout
java -cp tout Target &
PID=$!

$! holds the process id of the last background command. The examples below use $PID. When the examples are finished, kill $PID stops the program.

Example 6: jps -l

jps lists the JVM processes of the current user. The -l option prints the main class or jar path instead of only the short class name.

Terminal
jps -l
Output
7319 jdk.jcmd/sun.tools.jps.Jps
7261 Target

The first column is the process id and varies on every run. The second column is the main class. The line for jdk.jcmd/sun.tools.jps.Jps is jps itself, because it is also a JVM.

Option Meaning
-l Full main class name, or the jar path for java -jar
-v JVM arguments passed to the process
-m Arguments passed to main

jps reads the same attach information the other tools use. When a JVM does not appear in the list, the usual causes are a different operating system user, a container with a separate /tmp, or the option -XX:-UsePerfData on the target.

Example 7: jcmd

jcmd is the general entry point. Run without arguments it lists JVMs. Run with a process id and a command name, it asks that JVM to run a diagnostic command and prints the result. The next four calls use help, VM.flags, Thread.print, and GC.heap_info.

The list form:

Terminal
jcmd
Output
PID jdk.jcmd/sun.tools.jcmd.JCmd
PID Target

The help command lists what the target JVM supports (trimmed to the first 27 lines; the full list is longer):

Terminal
jcmd $PID help
Output
PID:
The following commands are available:
AOT.end_recording
Compiler.CodeHeap_Analytics
Compiler.codecache
Compiler.codelist
Compiler.directives_add
Compiler.directives_clear
Compiler.directives_print
Compiler.directives_remove
Compiler.memory
Compiler.perfmap
Compiler.queue
GC.class_histogram
GC.finalizer_info
GC.heap_dump
GC.heap_info
GC.run
GC.run_finalization
JFR.check
JFR.configure
JFR.dump
JFR.start
JFR.stop
JFR.view
JVMTI.agent_load
JVMTI.data_dump
ManagementAgent.start
ManagementAgent.start_local

jcmd $PID help GC.heap_info prints the description and options of one command. The list depends on the JDK version, so help is the reference for the JVM actually running.

VM.flags prints the JVM flags in effect, including values the JVM chose itself:

Terminal
jcmd $PID VM.flags
Output
PID:
-XX:-AOTInvokeDynamicLinking -XX:-AOTRecordTraining -XX:-AOTReplayTraining -XX:CICompilerCount=3 -XX:ConcGCThreads=1 -XX:G1ConcRefinementThreads=4 -XX:G1EagerReclaimRemSetThreshold=16 -XX:G1HeapRegionSize=2097152 -XX:G1RemSetArrayOfCardsEntries=16 -XX:G1RemSetHowlMaxNumBuckets=8 -XX:G1RemSetHowlNumBuckets=8 -XX:InitialHeapSize=224395264 -XX:-IntelJccErratumMitigation -XX:MarkStackSize=4194304 -XX:MarkStackSizeMax=536870912 -XX:MaxHeapSize=3588227072 -XX:MaxNewSize=2151677952 -XX:MinHeapDeltaBytes=2097152 -XX:MinHeapSize=8388608 -XX:NonNMethodCodeHeapSize=5836800 -XX:NonProfiledCodeHeapSize=122912768 -XX:ProfiledCodeHeapSize=122912768 -XX:ReservedCodeCacheSize=251662336 -XX:+SegmentedCodeCache -XX:SoftMaxHeapSize=3588227072 -XX:-THPStackMitigation -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC -XX:X86ICacheSync=3

No heap or collector option was given, so every value comes from the JVM's own sizing. -XX:+UseG1GC shows the collector, and -XX:MaxHeapSize=3588227072 shows the heap limit in bytes, chosen from the machine memory. The exact flags depend on the machine and the JDK build.

Thread.print prints all thread stacks. The filter below keeps the entry for worker-1:

Terminal
jcmd $PID Thread.print | grep -A8 '"worker-1"'
Output
"worker-1" #23 [7282] daemon prio=5 os_prio=0 cpu=218.76ms elapsed=2.99s tid=0x00007f5c18148290 nid=7282 waiting on condition  [0x00007f5bfcd85000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
	at java.lang.Thread.sleepNanos0(java.base@25.0.4.1/Native Method)
	at java.lang.Thread.sleepNanos(java.base@25.0.4.1/Thread.java:509)
	at java.lang.Thread.sleep(java.base@25.0.4.1/Thread.java:540)
	at Target.lambda$main$0(Target.java:15)
	at Target$$Lambda/0x0000000033040210.run(Unknown Source)
	at java.lang.Thread.runWith(java.base@25.0.4.1/Thread.java:1487)
	at java.lang.Thread.run(java.base@25.0.4.1/Thread.java:1474)

The header gives the thread name, id, priority, CPU time, and age. nid is the operating system thread id; here it equals the number in the [7282] field. The state TIMED_WAITING (sleeping) and the top frames show that this sample caught the thread inside Thread.sleep, called from line 15 of Target.java. The id, the address values, and the timings differ on every run.

GC.heap_info prints the heap layout:

Terminal
jcmd $PID GC.heap_info
Output
PID:
garbage-first heap   total reserved 3504128K, committed 221184K, used 104484K [0x000000072a200000, 0x0000000800000000)
 region size 2048K, 49 young (100352K), 5 survivors (10240K)

total reserved is the address space set aside for the heap. committed is the memory the JVM has asked the operating system for. used is what live and not yet collected objects occupy. G1 splits the heap into regions of equal size, here 2048K, and the second line counts the young regions and survivor regions. The values change as the program allocates.

Example 8: jstack

jstack prints thread stacks, the same data as jcmd $PID Thread.print. It is the older and shorter command, and it is the usual choice when diagnosing a hang or a deadlock.

Terminal
jstack $PID | grep -A6 '"worker-1"'
Output
"worker-1" #23 [7282] daemon prio=5 os_prio=0 cpu=236.98ms elapsed=3.28s tid=0x00007f5c18148290 nid=7282 waiting on condition  [0x00007f5bfcd85000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
	at java.lang.Thread.sleepNanos0(java.base@25.0.4.1/Native Method)
	at java.lang.Thread.sleepNanos(java.base@25.0.4.1/Thread.java:509)
	at java.lang.Thread.sleep(java.base@25.0.4.1/Thread.java:540)
	at Target.lambda$main$0(Target.java:15)
	at Target$$Lambda/0x0000000033040210.run(Unknown Source)

The dump begins with a line Full thread dump OpenJDK 64-Bit Server VM (...), then one block per thread. Two or three dumps taken a few seconds apart show whether a thread is moving or stuck. A thread that shows the same stack in every dump is waiting or blocked there.

When threads deadlock on monitors, the dump ends with a section Found one Java-level deadlock: that names each thread and the lock it holds and wants. The state BLOCKED means the thread waits for a monitor, WAITING and TIMED_WAITING mean it waits on wait, park, or sleep, and RUNNABLE means it is running or ready to run, including threads blocked in native I/O.

Example 9: jmap -histo

jmap -histo prints a histogram of objects on the heap: one row per class with an instance count and the bytes used.

Terminal
jmap -histo $PID | head -8
Output
 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:         20895      121954056  [B (java.base@25.0.4.1)
   2:           172        1798000  [Ljdk.internal.vm.FillerElement; (java.base@25.0.4.1)
   3:          1694         224208  java.lang.Class (java.base@25.0.4.1)
   4:          8681         208344  java.lang.String (java.base@25.0.4.1)
   5:          1452         163304  [Ljava.lang.Object; (java.base@25.0.4.1)
   6:          2023          54024  [I (java.base@25.0.4.1)

[B is the JVM name for byte[], so the top row is the arrays the worker-1 thread allocates. [I is int[], and [Ljava.lang.Object; is Object[]. The FillerElement row is a placeholder object that the collector uses to keep heap regions parseable. The table includes objects that are no longer reachable but not yet collected.

The live option forces a collection first, so the table counts only reachable objects:

Terminal
jmap -histo:live $PID | head -5
Output
 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:          9804       10876584  [B (java.base@25.0.4.1)
   2:          1694         224208  java.lang.Class (java.base@25.0.4.1)
   3:          8680         208320  java.lang.String (java.base@25.0.4.1)

The byte[] total dropped from about 122 MB to about 11 MB, because most arrays were garbage. The live option triggers a full collection, which pauses the application. On a production process, take the plain histogram first. Comparing two histograms taken some minutes apart shows which class count keeps growing, which is the first clue in a memory leak search. jcmd $PID GC.class_histogram prints the same table.

Example 10: jstat -gc

jstat reads counters the JVM publishes and prints them as a table. It does not pause the program, and it can repeat at an interval.

Terminal
jstat -gc $PID
Output
    S0C         S1C         S0U         S1U          EC           EU           OC           OU          MC         MU       CCSC      CCSU     YGC     YGCT     FGC    FGCT     CGC    CGCT       GCT   
        0.0      2048.0         0.0      1575.7      77824.0      22528.0      55296.0      11607.5      384.0      185.5     128.0      10.4     12     0.034     1     0.011     0     0.000     0.044

The size columns are in KB. A trailing C means capacity and a trailing U means used.

Column Meaning
S0C, S1C, S0U, S1U Survivor space capacity and use
EC, EU Eden (new object area) capacity and use
OC, OU Old generation capacity and use
MC, MU Metaspace (class metadata) capacity and use
YGC, YGCT Count and total seconds of young collections
FGC, FGCT Count and total seconds of full collections
CGC, CGCT Count and total seconds of concurrent collections
GCT Total collection time in seconds

Two other forms are common. -gcutil prints percentages, and a trailing interval and count repeat the sample:

Terminal
jstat -gcutil $PID 200 3
Output
  S0     S1     E      O      M     CCS    YGC     YGCT     FGC    FGCT     CGC    CGCT       GCT   
     - 76.94  81.58  20.99  48.30   8.14     12     0.034     1     0.011     0     0.000     0.044
     - 100.00  90.32  34.23  48.30   8.14     13     0.040     1     0.011     0     0.000     0.050
     -  84.93  91.67  38.04  48.30   8.14     14     0.041     1     0.011     0     0.000     0.051

The arguments 200 3 mean one sample every 200 milliseconds, three samples. The YGC column rises from 12 to 14 while the three rows print, which shows the allocation rate of the target: two young collections in under half a second. A rising FGC count with little freed memory points to a heap that is too small or a leak. The - in S0 means that survivor space is empty in G1.

Example 11: jfr record and jfr print/summary

Java Flight Recorder (JFR) records events from inside the JVM with low overhead: GC pauses, allocation samples, method samples, lock waits, and more. jcmd starts the recording, and the jfr tool reads the file.

Terminal
jcmd $PID JFR.start name=demo filename=/tmp/claude-0/cmd-l14/demo/rec.jfr duration=5s settings=profile
Output
PID:
Started recording 1. The result will be written to:

/tmp/claude-0/cmd-l14/demo/rec.jfr
Option Meaning
name=demo Label for the recording, used by JFR.check, JFR.dump, and JFR.stop
filename= File that receives the data when the recording ends
duration=5s Stop and write the file after five seconds
settings=profile Use the profile event set, which samples more often than default

After seven seconds the file exists. jfr summary counts events by type (the 17 largest types are shown):

Terminal
jfr summary rec.jfr | head -25
Output

 Version: 2.1
 Chunks: 1
 Start: 2026-10-03 15:18:59 (UTC)
 Duration: 5 s

 Event Type                              Count  Size (bytes) 
=============================================================
 jdk.GCPhaseParallel                      2447         65021
 jdk.ObjectAllocationSample               1491         20891
 jdk.PromoteObjectInNewPLAB                560          9854
 jdk.ModuleExport                          508          5437
 jdk.BooleanFlag                           496         15213
 jdk.ActiveSetting                         376          9700
 jdk.TenuringDistribution                  330          3369
 jdk.ModuleRequire                         156          1560
 jdk.InitialEnvironmentVariable            152          9106
 jdk.LongFlag                              137          4400
 jdk.UnsignedLongFlag                       93          3143
 jdk.SystemProcess                          91         11987
 jdk.GCReferenceStatistics                  88           880
 jdk.MetaspaceChunkFreeListSummary          88          1584
 jdk.GCPhasePauseLevel1                     66          2686
 jdk.UnsignedIntFlag                        48          1582
 jdk.InitialSecurityProperty                45          2243

The summary shows what the recording holds before any event is printed. The counts vary from run to run. jfr print --events prints events of one type, matched by the full or the short name:

Terminal
jfr print --events jdk.GCPhasePause rec.jfr | head -14
Output
jdk.GCPhasePause {
  startTime = 15:18:59.220 (2026-10-03)
  duration = 3.28 ms
  gcId = 19
  name = "GC Pause"
  eventThread = "VM Thread" (osThreadId = 7272)
}

jdk.GCPhasePause {
  startTime = 15:18:59.440 (2026-10-03)
  duration = 1.24 ms
  gcId = 20
  name = "GC Pause"
  eventThread = "VM Thread" (osThreadId = 7272)

Each block is one GC pause with its start time, length, and collection id. Method samples show where the CPU time goes. The option --stack-depth 3 limits the printed stack:

Terminal
jfr print --events jdk.ExecutionSample --stack-depth 3 rec.jfr | head -16
Output
jdk.ExecutionSample {
  startTime = 15:18:59.083 (2026-10-03)
  sampledThread = "worker-1" (javaThreadId = 23)
  state = "STATE_RUNNABLE"
  stackTrace = [
    Target.lambda$main$0() line: 14
    java.lang.Thread.run() line: 1474
  ]
}

jdk.ExecutionSample {
  startTime = 15:18:59.193 (2026-10-03)
  sampledThread = "worker-1" (javaThreadId = 23)
  state = "STATE_RUNNABLE"
  stackTrace = [
    Target.lambda$main$0() line: 14

Every sample lands on line 14, the line that allocates the array, which matches the program. The options --json and --xml print the same events in a structured form for scripts. For event sets, JMC and the viewer commands, and an async-profiler comparison, see JFR and async-profiler.

After this example, stop the target program:

Terminal
kill $PID

Example 12: JAVA_TOOL_OPTIONS

JAVA_TOOL_OPTIONS is an environment variable that every JVM start reads. Its contents act as extra JVM options, and the JVM prints a notice on standard error when it uses them. This helps when the java command line cannot be edited, as with a build tool or a test runner that starts a JVM.

Java
public class Props {
    public static void main(String[] args) {
        System.out.println("greeting=" + System.getProperty("greeting"));
        System.out.println("maxMemoryMB=" + Runtime.getRuntime().maxMemory() / (1024 * 1024));
    }
}
Terminal
JAVA_TOOL_OPTIONS="-Xmx64m -Dgreeting=hi" java Props.java
java Props.java
Output
Picked up JAVA_TOOL_OPTIONS: -Xmx64m -Dgreeting=hi
greeting=hi
maxMemoryMB=64
greeting=null
maxMemoryMB=3422

The first run applied a 64 MB heap limit and a system property. The second run, without the variable, used the default limit, which is a fraction of the machine memory (this value depends on the machine) and has no property. The variable is set only for the one command here because it is a prefix assignment. Exporting it affects every JVM the shell starts, including the compiler and the tools in this lesson, which then print the Picked up line too.

Variable Read by Notice printed
JAVA_TOOL_OPTIONS Every JVM start, including javac, jcmd, and jshell Yes
JDK_JAVA_OPTIONS The java launcher only Yes, prefixed with NOTE:
_JAVA_OPTIONS HotSpot JVMs, applied after JAVA_TOOL_OPTIONS (not an official option) Yes

When the same option appears in several places, the command line wins over JAVA_TOOL_OPTIONS for most flags. A value set by the variable is visible with java -XX:+PrintFlagsFinal -version, where the origin column reads command line:

Terminal
JAVA_TOOL_OPTIONS="-Xmx64m" java -XX:+PrintFlagsFinal -version 2>&1 | grep -E 'Picked|MaxHeapSize'
Output
Picked up JAVA_TOOL_OPTIONS: -Xmx64m
   size_t MaxHeapSize                              = 67108864                                  {product} {command line}
   size_t SoftMaxHeapSize                          = 67108864                               {manageable} {ergonomic}

The origin {command line} shows that the JVM treats options from this variable as if they had been typed on the command line.

Interview points

  • java -version writes to standard error and java --version to standard output; JAVA_HOME and PATH decide which JDK answers.
  • java Hello.java compiles one file in memory and runs it; programs with several source files need javac.
  • javac -d out writes class files into a directory tree that mirrors the package names, and java -cp out pkg.Main runs the class by its qualified name.
  • jar --create --main-class X -C dir . builds a runnable jar; without -C the entries get a wrong prefix and java -jar fails.
  • jps -l, jcmd without arguments, and jstat work for processes of the same operating system user; a different user or a separate /tmp hides the JVM.
  • jcmd <pid> help lists the diagnostic commands of that exact JVM; Thread.print, GC.heap_info, VM.flags, GC.class_histogram, and JFR.start cover most investigations.
  • A hang is diagnosed with two or three jstack dumps a few seconds apart; the Found one Java-level deadlock section names the threads and locks.
  • jmap -histo:live forces a full collection and pauses the application; the plain histogram does not, but counts unreachable objects too.
  • jstat -gcutil <pid> 1000 shows collection counts and times as they change; a rising FGC with little memory freed points to an undersized heap or a leak.
  • JFR records with low overhead from inside the JVM; jfr summary shows what a file contains and jfr print --events extracts one event type.
  • JAVA_TOOL_OPTIONS adds options to every JVM start, prints a Picked up line on standard error, and also affects tools such as javac and jcmd.

Summary

  • java --version prints the vendor build, the release, and the VM mode.
  • java File.java args compiles and runs a single source file without writing class files.
  • javac -d out creates the package directory tree under out; --release N fixes the API and language level.
  • jar --create --file app.jar --main-class pkg.Main -C out . produces a jar that java -jar starts.
  • jar --list --file app.jar lists entries, and META-INF/MANIFEST.MF is added automatically.
  • jshell -s script.jsh runs snippets and / commands from a file; lines such as /exit control the session.
  • jps -l prints process ids and main classes of JVMs owned by the current user.
  • jcmd <pid> <command> runs a diagnostic command in the target JVM; help lists the available ones.
  • jstack <pid> prints all thread stacks, with thread state, name, and top frames per thread.
  • jmap -histo <pid> prints instance counts and bytes per class; [B means byte[]; :live forces a collection first.
  • jstat -gc prints capacities and usage in KB plus collection counts and times; -gcutil prints percentages and accepts an interval and a count.
  • jcmd <pid> JFR.start writes a recording file, and jfr summary and jfr print --events read it.
  • JAVA_TOOL_OPTIONS adds JVM options to every JVM start and prints Picked up JAVA_TOOL_OPTIONS on standard error.