JDK vs JRE vs JVM: Internal Differences & Modular Runtime
For Java engineers, distinguishing between the Java Development Kit (JDK), Java Runtime Environment (JRE), and Java Virtual Machine (JVM) is essential for environment configuration, container optimization, and application deployment. While these three terms are often referenced together, they represent distinct architectural layers in the Java software ecosystem.
In this guide, we unpack the structural hierarchy of JDK, JRE, and JVM, explore core diagnostic tools packaged within the JDK, and demonstrate how to build minimal custom runtime images using jlink.
1. The Structural Hierarchy: JDK, JRE, and JVM
The relationship between JDK, JRE, and JVM is best understood as a nested superset hierarchy.
flowchart TD
subgraph JDK["Java Development Kit (JDK)"]
subgraph DevTools["Development & Diagnostic Tools"]
Compiler["javac (Compiler)"]
JCmd["jcmd / jstack / jmap"]
JFR["Java Flight Recorder (JFR)"]
Javadoc["javadoc / jar"]
end
subgraph JRE["Java Runtime Environment (JRE)"]
subgraph ClassLibs["Standard Class Libraries"]
BaseLib["java.base / java.util"]
Logging["java.logging / java.sql"]
end
subgraph JVM["Java Virtual Machine (JVM)"]
Engine["HotSpot Execution Engine"]
GC["Garbage Collector"]
JIT["JIT Compiler (C1 / C2)"]
end
end
end
Java Virtual Machine (JVM)
The JVM is the execution engine runtime. It loads bytecodes, manages runtime memory (Heap, Stack, Metaspace), executes garbage collection, and compiles hot bytecodes into native machine code. The JVM specification defines how virtual machine operations work, while HotSpot (OpenJ9, Eclipse OpenJ9) is the physical native implementation.
Java Runtime Environment (JRE)
The JRE is the execution package. It contains the JVM along with the core Java API class libraries (e.g., java.base, java.util) and runtime support files necessary to run a pre-compiled Java application.
Java Development Kit (JDK)
The JDK is the full-featured software development environment. It contains the complete JRE plus command-line development and diagnostic utilities (javac compiler, jcmd, jstack, jmap, jfr, javap).
2. Core JDK Diagnostic & Profiling Utilities
Production deployment environments require the JDK's diagnostic toolset to profile memory leaks, investigate high CPU thread spikes, and analyze thread dumps.
flowchart LR
Trouble["Production Issue Detected"] --> JCmdTool["jcmd <pid>\n(General VM Diagnostics)"]
Trouble --> JStackTool["jstack <pid>\n(Thread Dump / Deadlocks)"]
Trouble --> JMapTool["jmap -histo <pid>\n(Heap Allocation Histogram)"]
Trouble --> JFRTool["jfr / JMC\n(Low-Overhead Event Profiling)"]
| JDK Utility Tool | Primary Engineering Use Case |
|---|---|
javac |
Compiles Java source code (.java) into bytecode binary files (.class). |
javap |
Disassembles bytecode files into human-readable JVM instructions (e.g., javap -c -v ClassName). |
jcmd |
Sends diagnostic command requests to a running JVM process (e.g., triggering thread dumps or GC sweeps). |
jstack |
Prints thread stack traces for all active Java threads to diagnose deadlocks and thread contention. |
jmap / jhat |
Generates memory heap dumps (.hprof) for memory leak analysis in Eclipse MAT or JProfiler. |
jfr / JDK Mission Control |
Captures low-overhead execution metrics directly from the HotSpot JVM engine via Java Flight Recorder. |
3. Modern Modular Runtimes: Custom Docker Images with jlink
Historically, deploying a Java application required bundling a full 300MB+ JRE inside container images. With the introduction of the Java Platform Module System (JPMS) in Java 9, developers can use the jlink utility to analyze application module dependencies and assemble a custom, stripped-down runtime containing only the required modules.
flowchart LR
subgraph FullJRE["Full Monolithic JRE Image"]
JRE350["Standard JDK / JRE Bundle\n(350 MB Total Size)"]
end
subgraph MiniRuntime["jlink Minimal Custom Runtime"]
Mini35["Strip-Debug Custom Image\n(35 MB Total Size - 90% Footprint Reduction!)"]
end
# Step 1: Detect required JDK modules for your application JAR
jdeps --module-path target/lib --show-module-deps target/my-app.jar
# Output: java.base,java.sql,java.net.http
# Step 2: Create a minimal, custom runtime image using jlink
jlink \
--module-path $JAVA_HOME/jmods \
--add-modules java.base,java.sql,java.net.http \
--strip-debug \
--no-man-pages \
--no-header-files \
--compress=2 \
--output /opt/custom-mini-runtime
Using jlink reduces container image sizes from 350MB down to less than 40MB, drastically reducing container cold-start times and attack surface vulnerabilities in production Cloud Native deployment pipelines.
- To explore JVM execution engine interpreter and JIT compilation, read JVM Architecture & Execution Engine Core Overview.
- To learn about JVM execution lifecycle from startup to shutdown, read The JVM Execution Lifecycle.
- To master production JVM monitoring, heap dump analysis, and thread dumps, read JVM Monitoring, Profiling & Troubleshooting.