the jdk toolbox
javac, jar, javap, jdeps, jcmd, jdb. what's in bin/ and why.
- chapter
- chapter 12.01
- reading time
- 8 min read
a JDK is not one program, it's 30 programs in $JAVA_HOME/bin. you'll touch about eight of them every week: javac, java, jar and jshell to build and run, javap and jdeps to look inside compiled code, and jcmd and jfr to poke at a JVM that's already running.
the diagnostic ones all work the same way: find the pid, attach, ask.
the picture
think of a mechanic's van. there's the stuff for building the car (javac, jar), the key that starts it (java), and a diagnostic scanner you plug into a running engine without stopping it (jcmd). most people only ever open the first drawer.
the swiss army knife. sends commands to a live JVM: thread dumps, heap dumps, JFR, flags, GC.
$ jcmd 89793 help89793:The following commands are available:Compiler.codecacheGC.class_histogramGC.heap_dumpGC.heap_infoJFR.startJFR.dumpThread.printVM.flagsVM.native_memoryVM.uptime... (54 commands on jdk 25)$ jcmd 89793 VM.uptime11.573 s
jdeprscan for a preview of your next upgrade, and jcmd for the one diagnostic tool to learn first.ls $JAVA_HOME/bin on jdk 25 lists exactly 30. they're thin launchers: most of them start a small JVM that runs a class from a module like jdk.compiler or jdk.jcmd (that's why jps lists itself as jdk.jcmd/sun.tools.jps.Jps). so every tool here is also a java program, running on the JDK it ships with.
the build loop
you'll learn this one in a day and then never think about it again. a build tool (maven, gradle) runs exactly these steps for you.
hover a stage.
public class Hello {
public static void main(String[] args) {
String name = args.length > 0 ? args[0] : "world";
System.out.println("hello, " + name);
}
}$ javac -d out Hello.java
$ jar --create --file hello.jar --main-class Hello -C out .
$ java -jar hello.jar
hello, world
$ java Hello.java ada # no javac step at all
hello, adathat last line is the source launcher (java 11, and since 22 it handles programs spread over several files). it compiles in memory and runs. great for scripts and for this site, but it's still the same javac doing the work, it just never writes a .class to disk.
looking inside compiled code
two tools read class files without running anything. javap disassembles one class. jdeps maps what a whole jar depends on.
public static void main(java.lang.String[]);
Code:
0: aload_0
1: arraylength
2: ifle 11
5: aload_0
6: iconst_0
7: aaload
8: goto 13
11: ldc #7 // String world
13: astore_1
14: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
17: aload_1
18: invokedynamic #15,0 // InvokeDynamic #0:makeConcatWithConstants
23: invokevirtual #19 // Method java/io/PrintStream.println
26: returnsee line 18? "hello, " + name didn't become a StringBuilder. since java 9, javac emits one invokedynamic and lets the JVM pick the fastest way to concatenate at runtime. you can only learn that kind of thing by looking, and javap is how you look. the full instruction set is in bytecode, and the concat trick has its own page: string concatenation.
poking a JVM that's already running
this is the drawer worth learning properly. something is slow, stuck or eating memory in prod, and you can't restart it (restarting destroys the evidence). you don't need an agent or a special flag: every HotSpot JVM accepts commands from jcmd out of the box.
you run jcmd 4242 Thread.print. jcmd and the JVM are two separate processes. nothing is listening yet.
$TMPDIR instead of /tmp; it showed up there as srw------- while i attached.so the whole workflow is two commands: jps (or jcmd with no arguments) to get the pid, then jcmd <pid> <command>. jcmd <pid> help lists 54 commands on jdk 25. you need about six:
| the question | the command |
|---|---|
| which JVMs are running? | jcmd (or jps -l) |
| what is every thread doing right now? | jcmd <pid> Thread.print |
| what's filling the heap? | jcmd <pid> GC.class_histogram |
| give me the whole heap to analyze | jcmd <pid> GC.heap_dump /tmp/heap.hprof |
| what flags did it actually start with? | jcmd <pid> VM.flags |
| record everything for a minute | jcmd <pid> JFR.start duration=60s filename=app.jfr |
jstack, jmap and jinfo do some of the same jobs. they're older (java 5) and still work, but jcmd (java 7) is the one tool that does all of it, so that's the one to remember.
and a thread dump is where a deadlock confesses. two threads, two locks, opposite order:
Found one Java-level deadlock:
=============================
"alice":
waiting to lock monitor 0x00000075e149e3e0 (object 0x0000007018418118, a java.lang.Object),
which is held by "bob"
"bob":
waiting to lock monitor 0x00000075e149e4c0 (object 0x0000007018418108, a java.lang.Object),
which is held by "alice"the JVM found the cycle itself. you didn't have to read 30 stacks to spot it. why that happens and how to avoid it is in deadlocks.
the rest of the box
jshell(java 9) is a REPL. it's the fastest way to check what"a,b,,".split(",")actually returns. jshell has the tour.jwebserver(java 18) serves a folder over http in one command. for testing, docs and teaching. it's not hardened for production and says so.jlinkandjpackagebuild a runtime with only the modules you need, then wrap it into a native installer. that's the next-but-one chapter.jfrreads flight recordings. flight recorder is all about it.keytoolandjarsignerhandle keys, certificates and signed jars. you'll meetkeytoolthe first time a TLS handshake fails because a corporate CA isn't incacerts.
why are there still tools for RMI and serialVersionUID?+
because the JDK almost never deletes a tool without a deprecation cycle first. rmiregistry and serialver date from java 1.1 and still have users. the ones that did go away went loudly: appletviewer (gone in 11), jjs (the nashorn JavaScript shell, gone in 15), and jrunscript, which was deprecated for removal in 24 and deleted in 26. run it on 25 and it warns you first. jdeprscan gives your own code the same courtesy.
- the JDK is 30 small programs. learn javac, java, jar, jshell, javap, jdeps, jcmd and jfr.
javac --release Nis how you safely target an older java.javap -cshows what javac actually wrote. trust it over blog posts.- a live JVM accepts commands:
jcmd <pid> help. no restart, no agent, no setup. - thread dumps catch deadlocks for you. take a few, a few seconds apart.