Together, the Java Development Kit (JDK), the Java Virtual Machine (JVM), and the Java Runtime Environment (JRE) form a powerful trifecta of Java and Jakarta EE platform components for developing and running Java applications. They all work together to let developers build and run Java programs. I’ve previously introduced the JDK and JVM. In this quick overview, you’ll learn about the JRE, which is the runtime environment for Java.
Practically speaking, a runtime environment is a piece of software that is designed to run other software. As the runtime environment for Java, the JRE contains the Java class libraries, the Java class loader, and the Java virtual machine. In this system:
- The class loader is responsible for correctly loading classes and connecting them with the core Java class libraries.
- The JVM is responsible for ensuring Java applications have the resources they need to run and perform well in your device or cloud environment.
- The JRE is mainly a container for those other components, and is responsible for orchestrating their activities.
We’ll dig a lot deeper into how these components work together in the discussion that follows.
What is a runtime environment?
A software program needs to execute, and to do that, it needs an environment to run in. In the past, most software used the operating system (OS) as the runtime environment. The program ran inside whatever computer it was on and relied directly on operating system settings for resource access; resources like memory, disk access, and network access. The Java Runtime Environment changed all that, at least for Java programs. In the case of Java and other JVM-based languages, the JRE creates an intermediary between the operating system and the actual program. The JRE loads class files and starts a virtual machine (the JVM) that ensures there is access to memory and other system resources in a consistent form across many operating systems.
The Java Runtime Environment
We can look at software as a series of layers that sit on top of the system hardware. Each layer provides services that will be used (and required) by the layers above it. The Java Runtime Environment spawns a JVM, which is a software layer that runs on top of a computer’s operating system, providing additional services specific to Java. Figure 1 illustrates this arrangement.
The JRE smooths over the diversity of operating systems, ensuring that Java programs can run on virtually any operating system without modification. It also provides value-added services. Automatic memory management is one of the JRE’s most important services, ensuring that programmers don’t have to manually control the allocation and reallocation of memory.
In short, the JRE is a sort of meta-OS for Java, along with other JVM languages like Scala and Groovy. It’s a classic example of abstraction, abstracting the underlying operating system into a consistent platform for running Java applications.
How the JRE works with the JVM
A Java virtual machine is a running software system responsible for executing live Java programs. The JRE is the on-disk software component that takes your compiled Java code (the code is compiled with the JDK), combines it with the required libraries, and starts the JVM to execute it.
The JRE contains libraries and software that your Java programs need to run. As an example, the Java class loader is part of the JRE. This important piece of software loads compiled Java code into memory and connects the code to the appropriate Java class libraries (a process known as linking).
In the layered view I just described, the JVM is created by the JRE. From a package perspective, the JRE contains the JVM, as shown in Figure 2. The JVM is part of the JRE—it’s the active, running part that the JRE creates to host programs. The JRE takes static assets and turns them into a running JVM hosting the running program.
Installing and using the JRE
While there is a conceptual side to the JRE, in real-world practice it’s just software installed on a computer, whose purpose is to run your Java programs. As a developer, you’ll mostly work with the JDK and JVM, because those are the platform components you use to develop and run your Java programs. As a Java application user, you would be more involved with the JRE, which lets you run those programs.
Java 9 restructured the Java platform so that the JRE is now only available as part of a JDK. You can deliver a bundled JRE with your applications when you want to ship a consumer application, using JLink. Such a bundle contains all the necessary components to run the program. For our purposes, we’ll use a JRE inside the JDK. You can download the latest JDK for your system from Oracle’s Java SE page. Windows and macOS have automated installers that will manage the details (like setting the path). On Linux, a nice option is to use SDKMan. In any event, you’ll want to have the JRE available from your command line so that you can use the java
command.
Versions of the JRE
The Java Runtime Environment is updated for each new version of Java, and its version numbers align with the Java platform versioning system, so for example JRE 1.19 runs Java 19.
Many computers run a JRE developed for Java SE, which is able to run any Java application regardless of how it was developed. Most mobile devices come with a JRE for Java ME, which is pre-installed on the mobile device and is not available for download. Going forward, applications bundled with their own JRE via JLink will become the norm.
Once you’ve downloaded the JDK, you can interact with the contained JRE on the command-line by typing java -version
, which will tell you what version is installed. (On POSIX systems, you can always check the installed location with the command, which java
.)
The JRE in devops
The JRE is not very noticeable in the development stage, where it mostly just runs your programs in the OS or IDE of your choice. It plays a slightly more prominent role in devops and systems administration because the JRE is used for monitoring and configuration.
Basically, the JRE provides the “knobs” you would use to configure and control the characteristics of a Java application. Memory usage is a prime example, the bread and butter of systems administration. While memory usage is always important, it’s vital in cloud configurations, and devops is a cloud-based approach to building and running software. If you’re working in a devops environment, or interested in branching out into devops, it’s a good idea to understand how Java memory works and how it’s monitored in the JRE.
Java memory and the JRE
Java memory consists of three components: the heap, stack and metaspace (which was previously called permgen).
- Metaspace is where Java keeps your program’s unchanging info like class definitions.
- Heap space is where Java keeps variable content.
- Stack space is where Java stores function execution and variable references.
Java memory management since Java 8
Until Java 8, metaspace was known as permgen. Besides being a much cooler name, metaspace is a significant change to how developers interact with Java’s memory space. Previously, you would use the command java -XX:MaxPermSize
to monitor the size of permgen space. From Java 8 forward, Java automatically increases the size of the metaspace to accommodate your program’s meta-needs. Java 8 also introduced a new flag, MaxMetaspaceSize
, which you can use to limit the metaspace size.
Configuring heap space
Heap space is the most dynamic part of the Java memory system. You can use the -Xms
and -Xmx
flags to tell Java how big to start the heap, and how big to allow it to become. Understanding how to tune these flags for specific program needs is an important aspect of memory management in Java. The ideal is to make the heap big enough to attain the most efficient garbage collection. That is, you want to allow enough memory to let the program run, but you do not want it to be any bigger than necessary.
Configuring stack space
Stack space is where function calls and variable references are queued. Stack space is the source of the second-most-notorious exception in Java programming: the StackOverflowError (the first is the NullPointerException). The stack overflow exception indicates that you’ve run out of stack space because too much of it has been reserved. Usually, you’ll get a stack overflow when a method or methods call each other in a circular fashion, thereby devoting an ever-growing number of function calls into the stack.
You use the -Xss
switch to configure the stack starting size. The stack then grows dynamically according to the program’s needs.
Java application monitoring
Although application monitoring is a function of the JVM, the JRE provides configuration options, which are the necessary baseline for monitoring. A variety of tools are available for monitoring Java applications, from the classics (like the Unix command top
) to sophisticated remote monitoring solutions like Oracle’s infrastructure monitoring.
In between these options are visual profilers like VisualVM that allow for inspecting a running JVM. These tools enable tracking down hotspots and memory leaks, as well as watching overall memory consumption in your system.
Conclusion
The Java Runtime Environment is the on-disk program that loads Java applications for the JVM to execute. A JRE is included by default when you download the JDK, and each JRE includes the core Java class libraries, a Java class loader, and a JVM. It’s helpful to understand how the JVM, JDK and JRE interact, especially for working in cloud and devops environments. In these environments, the JRE takes a stronger role in monitoring and configuration than it would in traditional Java application development.
This story, “What is the JRE? Introduction to the Java Runtime Environment” was originally published by
Copyright © 2022 IDG Communications, Inc.