Unboxing the JDK

By Alys Brooks

It’s easy to forget the Java Development Kit is, in fact, a kit.  Many Clojure developers, myself included, rarely work with commands like java directly, instead using lein, boot, or clojure. Often we don’t even use the Java standard library directly in favor of idiomatic wrappers.

There are a lot of advantages to staying in the Clojure level. Often, Clojure-specific tools ergonomically support common practices like live-reloading, understand Clojure data structures, and can tuck away some of the intermediate layers of Clojure itself that aren’t a part of your application.

But the Java tools are still useful. So, in the spirit of a YouTube unboxing video, let’s take a look at what’s in the JDK box. We’ll be looking at OpenJDK 16, since later versions of the JDK remove some legacy tools, like appletview, which aren’t useful to Clojure developers today anyway.

The tools in post are listed roughly in order of importance.

The usual suspects

The two tools Java developers use most are java and javac. There’s a good chance you’re familiar with these (and if you are, feel free to skip ahead!) from doing Java development.

java starts the JVM, the program that converts Java bytecode into commands that platform can run natively, collects garbage, profiles and optimizes the running program, and provides information to monitoring tools. When you pass JVM arguments to tools like the Clojure CLI, they’re sent to java.

javac compiles Java source code into Java bytecode. Clojure projects don’t actually use javac unless they include Java source code; Clojure has its own compiler that emits Java bytecode using a subset of ASM.

You’re probably also familiar with javadoc, even if you haven’t heard of it. As the default documentation software for Java, it’s responsible for all of the Java Standard Library docs, plus many third party libraries  (You may also remember it for generating pages with HTML frames well into the 00s.) Clojure doesn’t have an official documentation tool, but options exist: Autodoc, which is used for the clojure. namespaces plus a few others, and Cljdoc, which is used by many open source Clojure(Script) libraries, including Lambda Island’s.

Monitoring

Regardless of what monitoring to do, start by running jps to identify the process ID. Unfortunately, most CLojure apps will be listed as main because that’s the standard entry point. If jps is ambiguous, you can also use jcmd, which will list the process ID and the command instead.

From that ID, a world of tools opens up:

  • Running jstack ID will show you all the running threads in that process. This is particularly handy when your application is taking longer to run than expected.
  • Running jcmd ID plus commands let you find out specific information about your running process, like GC stats: jcmd ID gc.info.
  • jconsole has much of the same monitoring, just in a GUI format.

Miscelleanous helpers

jshell (as in “java shell”, although I can’t help reading it as “JS hell”) is a Java REPL. While the Clojure REPL makes a pretty good Java REPL, thanks to its interop capabilities, if you’re interested in how something works in Java, jshell can be a handy choice.

jdeps shows dependencies of JARs, class files, or folders containing them.

jdb is a commandline debugger for Java.

Two JAR tools

While you can create JARs with jar, it’s probably a better idea to leave the JAR creation to your build tool and use jar solely to examine the contents. Note that JARs are actually zip files, so you can actually use any tool that opens zip files.

jarsigner, well, signs JARs.

Experimental tools

The following are experimental, so while they may be useful for debugging, you probably don’t want to build them into scripts. They’re also slightly redundant compared with jcmd, jconsole, and visualvm, but you may prefer their format instead:

  • jinfo dumps a lot of details about the current jvm, including the class path, JVM arguments (including the defaults), and more.
  • jstat and jstatd provide many of the same monitoring options but using sampling.
  • jmap shows statistics about all the classes loaded by a program and also lets you dump your program’s heap. It may be useful as an initial look into your application’s memory usage, but isn’t helpful for deep exploration on its own, since there’s not much detail. You can open the dumps in jhat or visualvm for further analysis.
  • jhat lets you analyze heap dumps through a very spartan web browser interface.

jaotc is an experimental ahead-of-time compiler. It’s also on its way out so I won’t say much more about it. GraalVM is the suggested alternative, and there’s lots of information and examples on using it for Clojure projects.

jlink is a bit of a dark horse. It lets you create custom runtimes with just the parts your application needs. This doesn’t make a lot of sense in scenarios where more than run java program is running on a platform, but for Docker images and other situations where your program runs with its own JRE, it might be worth it to slim down the JRE.

jrunscript runs various script files. To run a script file written in a language other than JavaScript, you need to provide a JAR. You probably already have a good runtime for any scripting langauge you’d use, so this doesn’t seem useful except in cases where you have the JDK available and little else.

jhsdb is similar to jdb but for crashed JVMs.

And one more thing

For many versions, the JDK contained jvisualvm, a Java profiler. It’s still available, just not as a part of the JDK. It has a much nicer interface than jconsole and more functionality, particularly profiling and sampling, so consider downloading it if you’re doing significant performance analysis.

When running it on Linux, you may want to provide --laf com.sun.java.swing.plaf.gtk.GTKLookAndFeel—on my machine, the default “look and feel” selects a tiny font.

Packing up

One interesting thing is the many ways these commands are modeled after C and Unix generally: jar after tar, jdb after gdb, javac after cc, and so on. This isn’t a game-changing insight, but is interesting historically. Considering Java’s place in programming language history, it makes sense. In the mid-90s, virtual machines , while not new, were much less common than they are today and C and C++ were much more dominant, so drawing a comparison made things easier for people coming from C or C++ and probably increased its credibility. By the time Clojure emerged, tools like Ant and Maven were already widespread in the Java ecosystem, so Leiningen had more predecessors to learn from.