The Java Development Kit (JDK) ships with a rich set of command-line diagnostic and monitoring tools that are often overlooked by application developers.
While libraries and frameworks help us build features faster, it is these standard JDK tools that become indispensable when we need to understand what a running JVM is actually doing.
Tools such as jps, jinfo, jstack and jmap allow us to inspect live Java processes, analyze JVM configuration, troubleshoot performance issues, and diagnose memory or threading problems – all without modifying application code.
They are lightweight, powerful, and available out of the box with every JDK installation.
1. Java Virtual Machine Process Status Tool – jps
The jps tool lists all the running jvm.
Here below we have a Test java file.
It keeps waiting until there is an input.
package test;
import java.util.Scanner;
public class JpsTest {
public static void main(String[] args) {
try (Scanner scanner = new Scanner(System.in)) {
System.out.println("Enter something: ");
String input = scanner.nextLine();
System.out.println("You entered: " + input);
}
}
}
We run it by :
java -Xms128m -Xmx128m test.JpsTest ovo
We can then use jps to find the jvm of this test :
jps
jps // list all java processes and their main classes
20461 JpsTest
jps -l
jps -l // list all java processes with package names + class names
20461 test.JpsTest
jps -v
jps -v // list jvm parameters
20461 JpsTest -Xms128m -Xmx128m
jps -q
jps -q // list only the process IDs
20461
jps -m
jps -m // list parameters of main method
20461 JpsTest ovo
When using jps, it has to pay attention that if the jvm disable below flag, jps will not work on it.
-XX:-UsePerfData
For instance, if we run the test like below, jps will not detect it :
java -Xms128m -Xmx128m -XX:-UsePerfData test.JpsTest ovo
2. Configuration Info – jinfo
The jinfo tool prints java configuration information for a given Java process.
Configuration information includes java system properties and java virtual machine command line flags.
jinfo pid // print system properties and jvm flags
jinfo -sysprops pid // print system properties
jinfo -flags pid // print jvm flags
jinfo -flag PrintGC pid // pring specific jvm flags
It allows also to modify jvm flags for a given java process.
jinfo -flag +PrintGC pid // enable/disable jvm flags
jinfo -flag name=value pid // reset jvm flags
It has to pay attention that not all the flags can be modified for the given java process.
To find out what flags can be modified, we can run below :
java -XX:+PrintFlagsFinal -version | grep -i manageable
-XX:+PrintFlagsFinal displays what options HotSpot ended up using for running java process on your computer according to its configurations (memory, system etc).
-XX:+PrintFlagsInitial displays what options were provided to HotSpot initially, before HotSpot has made its own adjustments.
Comparing the results of -XX:+PrintFlagsFinal to -XX:+PrintFlagsInitial can obviously be helpful in understanding adjustments that HotSpot has made for your computer.
Another way is to use directly -XX:+PrintCommandLineFlags to print out the adjustments.
3. Java Virtual Machine Statistics Monitoring Tool – jstat
The jstat tool displays performance statistics for a running jvm.
It is a very good alternative for monitoring a running jvm if we can not run visual tools like jconsole, jvisualvm or jmc.
Its syntax of usage is below :
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
The option is an aspect on which we can monitor.
Below are all the options :
jstat -options
-class
-compiler
-gc
-gccapacity
-gccause
-gcmetacapacity
-gcnew
-gcnewcapacity
-gcold
-gcoldcapacity
-gcutil
-printcompilation
For our scan test class, we use below to see its classes information :
jstat -class 6543
Loaded Bytes Unloaded Bytes Time
779 1939.0 0 0.0 0.05
If we add an interval of 1 second (1000 milliseconds), it will keep monitoring and provide an output every second :
jstat -class 6543 1000
Loaded Bytes Unloaded Bytes Time
779 1939.0 0 0.0 0.05
779 1939.0 0 0.0 0.05
779 1939.0 0 0.0 0.05
779 1939.0 0 0.0 0.05
779 1939.0 0 0.0 0.05
We can also add -t to show timestamp :
jstat -class -t 6543 1000
Timestamp Loaded Bytes Unloaded Bytes Time
215.8 779 1939.0 0 0.0 0.05
216.9 779 1939.0 0 0.0 0.05
217.8 779 1939.0 0 0.0 0.05
We use -h3 to show the header every 3 lines of output :
jstat -class -t -h3 6543 1000
Timestamp Loaded Bytes Unloaded Bytes Time
298.6 779 1939.0 0 0.0 0.05
299.7 779 1939.0 0 0.0 0.05
300.7 779 1939.0 0 0.0 0.05
Timestamp Loaded Bytes Unloaded Bytes Time
301.6 779 1939.0 0 0.0 0.05
302.6 779 1939.0 0 0.0 0.05
303.7 779 1939.0 0 0.0 0.05
Timestamp Loaded Bytes Unloaded Bytes Time
304.7 779 1939.0 0 0.0 0.05
305.6 779 1939.0 0 0.0 0.05
306.7 779 1939.0 0 0.0 0.05
We can also specify a count = 4 for the interval value, in this way, it will stop monitoring after count intervals :
jstat -class -t -h3 6543 1000 4
Timestamp Loaded Bytes Unloaded Bytes Time
379.2 779 1939.0 0 0.0 0.05
380.2 779 1939.0 0 0.0 0.05
381.2 779 1939.0 0 0.0 0.05
Timestamp Loaded Bytes Unloaded Bytes Time
382.2 779 1939.0 0 0.0 0.05
The options of jstat can be divided into 3 categories: class, gc and compiler.
The most useful one is gc.
Here below we have another gc test class.
It will fill a list with 100KB every time.
We run it with limited memory = 60MB.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* -Xms60m -Xmx60m -XX:SurvivorRatio=8
*/
public class GCTest {
public static void main(String[] args) throws InterruptedException {
List<byte[]> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(new byte[1024 * 100]); // 100KB
TimeUnit.MILLISECONDS.sleep(120);
}
}
}
With gcutil option, it keeps monitoring the percentage of usage for java heap of our test.
We can see that s0,s1,eden and old’s usage keeps growing.
jstat -gcutil -h1 8738 1000
S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
- - 68.18 0.00 - - 0 0.000 0 0.000 0 0.000 0.000
S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
- - 68.18 0.00 - - 0 0.000 0 0.000 0 0.000 0.000
S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
- - 72.73 0.00 - - 0 0.000 0 0.000 0 0.000 0.000
S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
- - 72.73 0.00 - - 0 0.000 0 0.000 0 0.000 0.000
S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
- - 72.73 0.00 - - 0 0.000 0 0.000 0 0.000 0.000
S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
- - 77.27 0.00 - - 0 0.000 0 0.000 0 0.000 0.000
S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
- - 77.27 0.00 - - 0 0.000 0 0.000 0 0.000 0.000
S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
- - 81.82 0.00 - - 0 0.000 0 0.000 0 0.000 0.000
S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
- - 81.82 0.00 - - 0 0.000 0 0.000 0 0.000 0.000
S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
- - 86.36 0.00 - - 0 0.000 0 0.000 0 0.000 0.000
4. Memory Map – jmap
The jmap tool prints heap memory details of a given java process, it also generates heap memory dump file.
Its basic usage is below :
/**
* print heap summary
* gc algorithm, head configuration, heap usage ...
*/
jmap -heap pid
/**
* print histogram of only live java objects
*/
jmap -histo:live pid
/**
* print histogram of all java objects
*/
jmap -histo pid
/**
* dump only live objects into file
*/
jmap -dump:live,format=b,file=/home/hello/heap.bin pid
/**
* dump all objects into file
*/
jmap -dump:format=b,file=/home/hello/heap.bin pid
To generate a heap dump file automatically, we can include below jvm flags into process :
-XX:+HeapDumpOnOutOfMemoryError
When there is OOM, it will generate heap dump in hprof binary format.
The dump file path can be set up by below flag :
-XX:HeapDumpPath=/path/to/a/folder/in/which/hprof/file/will/be/generated
5. Stack Trace – jstack
The jstack tool prints stack traces of threads for a given java process.
Its basic usage is quite simple :
// prints stack trace of threads
jstack pid
// prints additional information about locks
jstack -l pid
// prints mixed mode (both Java and native C/C++) stack traces
jstack -m pid
// force a stack dump when 'jstack [-l] pid' does not respond
jstack -F pid
If there is dead lock, jstack will show it clearly.
For instance, we have created a new dead lock test class like below :
public class DeadLockTest {
public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
Thread t1 = new Thread() {
@Override
public void run() {
synchronized (lock2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
}
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
synchronized (lock1) {
synchronized (lock2) {
}
}
}
};
t1.start();
t2.start();
}
}
In its thread dump, we can see that it points out clearly the dead lock :
Found one Java-level deadlock:
=============================
"Thread-0":
waiting to lock monitor 0x000070bcac0023f0 (object 0x0000000716e19120, a java.lang.Object),
which is held by "Thread-1"
"Thread-1":
waiting to lock monitor 0x000070bcb0000f30 (object 0x0000000716e19130, a java.lang.Object),
which is held by "Thread-0"
Java stack information for the threads listed above:
===================================================
"Thread-0":
at DeadLockTest$1.run(DeadLockTest.java:15)
- waiting to lock <0x0000000716e19120> (a java.lang.Object)
- locked <0x0000000716e19130> (a java.lang.Object)
"Thread-1":
at DeadLockTest$2.run(DeadLockTest.java:26)
- waiting to lock <0x0000000716e19130> (a java.lang.Object)
- locked <0x0000000716e19120> (a java.lang.Object)
Found 1 deadlock.
6. The Java Debugger – jdb
The jdb tool is a simple command-line debugger for java classes.
Below is a random test class, it generates and prints a random number every second :
import java.util.concurrent.TimeUnit;
/**
* -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9999
*/
public class RandomTest {
public static void main(String[] args) throws InterruptedException {
while (true) {
TimeUnit.SECONDS.sleep(1);
double random = Math.random();
System.out.println(random);
}
}
}
To start a jdb session, we can simply type below command :
jdb
Then, we can run this test by using jdb instead of using java.
jdb
Initializing jdb ...
> run
run RandomTest
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
>
VM Started: 0.9044621651550742
0.8411180472249474
0.48603949395940427
0.6269975843037852
0.47439672333174765
0.1544324678638752
0.5980614351185896
0.8553696759415511
0.31832192663004333
0.8566054438019979
0.2524433918307383
0.8335153247466884
0.5185678600501409
0.36907553357795686
0.650672941067145
0.23954825143081027
0.541708051381984
0.01056402244861343
0.5634431933710599
0.2919525324829373
We can also start the application by using java command with remote debug on a given port :
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9999 RandomTest
Then we can start a jdb session by attaching to the port :
jdb -attach 9999
jdb -attach 9999
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
>
In a jdb session, we can set a breakpoint by using :
- stop in <class>.<method>
- stop in <class>:<line>
The application will be suspended when it hits the breakpoint.
jdb -attach 9999
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
> stop in RandomTest.main
Set breakpoint RandomTest.main
>
Breakpoint hit: "thread=main", RandomTest.main(), line=9 bci=0
main[1]
Then we can use step to execute current line, and use print to see the value of a variable.
jdb -attach 9999
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
> stop in RandomTest.main
Set breakpoint RandomTest.main
>
Breakpoint hit: "thread=main", RandomTest.main(), line=9 bci=0
main[1] step
>
Step completed: "thread=main", RandomTest.main(), line=10 bci=7
main[1] step
>
Step completed: "thread=main", RandomTest.main(), line=11 bci=11
main[1] print random
random = 0.8594332173979783
main[1]
To list all breakpoints, we can use clear, we can also use eval to evaluate an expression.
jdb -attach 9999
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
> stop in RandomTest.main
Set breakpoint RandomTest.main
>
Breakpoint hit: "thread=main", RandomTest.main(), line=9 bci=0
main[1] step
>
Step completed: "thread=main", RandomTest.main(), line=10 bci=7
main[1] step
>
Step completed: "thread=main", RandomTest.main(), line=11 bci=11
main[1] clear
Breakpoints set:
breakpoint RandomTest.main
main[1] eval random+1
random+1 = 1.703972603279386
main[1]
If we want to delete a breakpoint, we can use clear <breakpoint> and using resume will continue the execution of application until hit the next breakpoint.
jdb -attach 9999
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
> stop in RandomTest.main
Set breakpoint RandomTest.main
>
Breakpoint hit: "thread=main", RandomTest.main(), line=9 bci=0
main[1] clear RandomTest.main
Removed: breakpoint RandomTest.main
main[1] resume
All threads resumed.
>
7. Java Diagnostic Command Tool – jcmd
The jcmd tool sends diagnostic command requests to a running jvm.
It is like a swiss knife because it includes the features of other jdk tools.
Below is its basic usage :
// list jvm processes
jcmd
// list all commands for selected jvm process
jcmd pid
// get command information for selected jvm process
jcmd pid <command> help
// execute selected command for selected jvm process$
jcmd pid <command>
Below is the list of commands for a selected jvm :
jcmd 7803
7803:
The following commands are available:
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
ManagementAgent.status
ManagementAgent.stop
System.dump_map
System.map
System.native_heap_info
System.trim_native_heap
Thread.dump_to_file
Thread.print
Thread.vthread_pollers
Thread.vthread_scheduler
VM.cds
VM.class_hierarchy
VM.classes
VM.classloader_stats
VM.classloaders
VM.command_line
VM.dynlibs
VM.events
VM.flags
VM.info
VM.log
VM.metaspace
VM.native_memory
VM.set_flag
VM.stringtable
VM.symboltable
VM.system_properties
VM.systemdictionary
VM.uptime
VM.version
help
For instance, Thread.print is to print thread dump like jstack.
GC.class_histogram is to print histogram of classes like jmap.
M.flags is to print vm flags like jinfo …
For example, below is the output for VM.uptime :
jcmd 7803 VM.uptime
7803:
704.094 s
Below is for VM.version :
jcmd 7803 VM.version
7803:
Java HotSpot(TM) 64-Bit Server VM version 25.0.1+8-LTS-27
JDK 25.0.1