In this article, I will explain Java tools such as JDK, JRE, and JVM, followed by Java Strings, the String pool, String immutability, the runtime memory area, the class loader, reference types, the class stack, the heap and stack, and why Java is platform independent.
♦Before discussing the JVM architecture, let’s first understand how a Java application runs on a computer.
Computers follow the von Neumann architecture, which is the foundation of modern computing. This architecture allows programs stored in memory to be executed as needed. Programs are executed by the processor, while applications are stored in memory. When a program runs, it is loaded into RAM for execution.
Normally, when an application is executed, the source code is compiled into machine code, which the computer can understand and execute. However, in Java, the Java compiler compiles the source code into bytecode instead of directly converting it into machine code.
Let’s see how it happen,
Machines cannot directly understand or execute bytecode, which is why the Java Virtual Machine (JVM) is used. The JVM is designed to interpret and execute bytecode. When Java code is written, it is first compiled into bytecode by the Java compiler. This bytecode is then loaded into the JVM. Inside the JVM, the Just-In-Time (JIT) compiler translates the bytecode into machine-specific instructions, which are then passed to the interpreter for execution. The interpreter executes these instructions and communicates with the processor to run the program. Throughout this process, the JVM optimizes the code, enhancing the efficiency and performance of the program over time.
♦What is JIT compiler?The Just-In-Time (JIT) compiler is a component of the Java Virtual Machine (JVM) that plays a crucial role in improving the performance of Java applications. It works by translating bytecode, which is an intermediate representation of the Java code, into native machine code at runtime. This translation happens just before the code is executed, hence the name “Just-In-Time.”
As the JIT compiler processes the bytecode, it identifies and translates frequently executed instructions into machine code. Once these instructions are translated, they are stored in memory. This means that the same instructions do not need to be translated repeatedly during subsequent executions. By reusing the already translated machine code, the JIT compiler significantly reduces the overhead of interpretation, leading to faster execution and enhanced efficiency over time. This dynamic optimization process helps improve the overall performance of Java applications as they run.
Why java platform independent?Java is considered platform-independent because its code is compiled into bytecode, which can run on any operating system with a Java Virtual Machine (JVM). Unlike other languages like C, where compiled code is specific to the operating system and processor architecture, Java’s bytecode can be executed on different operating systems without requiring recompilation. This allows Java applications to be developed once and run anywhere, making them highly portable and efficient across multiple platforms.
Ok now let’s see what is this JDK, JRE and JVM
- JDK — Java Development Kit
- JRE — Java Runtime Environment
- JVM — Java Virtual Machine
♦JDK — Java Development KitThe JDK (Java Development Kit) is a software development kit used for building Java applications. It contains both the JRE (Java Runtime Environment), which is required to run Java programs, and a set of essential development tools. These tools include the Javac compiler for compiling Java code, the Java launcher for executing applications, the Jar utility for packaging files into JAR format, and the Javadoc generator for creating documentation, among others.
There are multiple vendors providing different distributions of the JDK, each offering unique features and support options.
- Oracle JDK — The official version from Oracle, available with commercial support.
- OpenJDK — The open-source reference implementation of the Java platform.
- AdoptOpenJDK (Eclipse Adoptium) — A widely used, free OpenJDK distribution.
- Amazon Corretto — A production-ready, free JDK from Amazon.
- IBM Semeru (J9 JDK) — IBM’s optimized OpenJDK distribution.
- SAP Machine — SAP’s OpenJDK build for enterprise applications.
- Red Hat OpenJDK — A supported OpenJDK version from Red Hat.
- Azul Zulu — A performance-optimized OpenJDK with commercial support.
Each vendor provides variations of the JDK, with some offering long-term support (LTS) and additional optimizations suited for different use cases.
JRE — Java Runtime EnvironmentThe JRE (Java Runtime Environment) is the core component needed to run Java applications. It includes the JVM (Java Virtual Machine), which is responsible for executing Java bytecode, as well as a set of essential class libraries and runtime tools. These libraries include standard packages like java.lang, java.util, java.math, and various other utilities that Java programs rely on during execution. While the JRE is sufficient for running already developed Java applications, it does not include the development tools needed for writing or compiling Java code. For development, the JDK is required.
JVM — Java Virtual MachineThe JVM (Java Virtual Machine) is an abstract computing machine that enables a computer to run Java programs as well as programs written in other languages that are compiled into Java bytecode. It serves as a set of rules and specifications that define how bytecode should be executed. This flexibility allows developers to create their own implementations of the JVM. Besides Java, languages such as Scala, Kotlin, Groovy, and Clojure can also be compiled into Java bytecode, enabling them to run on the JVM and benefit from its features, such as garbage collection and platform independence.
♦Image from GeeksforGeeksIn the image above, you can see various components of the JVM, including the Class Loader, JVM Memory (also known as the Runtime Memory Area), Stacks, Heap, and Execution Engine. These components play a crucial role in the execution of Java programs. I will now describe some of the most important components in detail. Let’s go through them one by one.
Class LoaderThe Class Loader in Java is responsible for loading .class files (bytecode) into the JVM for execution. When Java code runs, it is first compiled into bytecode (.class files), and the Class Loader loads these files into memory. It has three main parts.
- Bootstrap Class Loader — Loads core Java classes from the rt.jar (like java.lang and java.util).
- Extension Class Loader — Loads classes from the ext directory (lib/ext in older JDKs).
- Application Class Loader — Loads classes from the application’s classpath (src or bin folders).
This hierarchy ensures Java programs can dynamically load and execute classes as needed.
Execution EngineThe Execution Engine is responsible for running the loaded bytecode in the JVM. It translates bytecode into machine code that the computer can understand. This process is handled by the Interpreter, which converts and executes bytecode line by line. To improve performance, the Just-In-Time (JIT) Compiler helps by compiling frequently used bytecode into native machine code, making execution faster.
Runtime Memory AreaThe Runtime Memory Area in the JVM manages memory during program execution. The Heap is the most important and frequently monitored memory area, as it stores objects and dynamically allocated data. If the heap memory is insufficient, issues like OutOfMemoryError can occur. Proper monitoring and optimization of the heap help ensure smooth execution of Java applications.
Method Area, Stack and HeapThe Method Area in the JVM stores essential information needed to run a program. It includes details about class structures, method names, variables, and constructors. This area helps set up the environment required for executing Java programs by keeping metadata and shared runtime information.
The Stack in the JVM follows an ordered structure for method execution, known as the Call Stack. When a method is called, a new frame is pushed onto the stack, and when the method completes, its frame is removed. This order ensures that methods execute and return in a structured manner, managing function calls efficiently.
The Heap in the JVM is where objects and their references are stored. When an object is created using the new keyword, it is allocated memory in the heap. This area allows dynamic memory allocation and is managed by Garbage Collection to free up unused objects.
Ok now let’s see how Strings handle in the JVM…………
♦In Java, Strings are objects and belong to the reference data type. They are immutable, meaning their values cannot be changed once created. Since Java programs create many string objects, a String Pool is introduced inside the Heap to optimize memory usage. The String Pool stores unique string literals, ensuring that duplicate strings share the same memory reference instead of creating new objects. This improves efficiency and reduces memory consumption.
Reference TypesIn Java, objects are stored in the heap, while their references are stored in the stack. A reference is a memory address that points to the object’s location in the heap. By default, object references are null until assigned. When an object is passed to a method, it is passed by reference, meaning changes made inside the method affect the original object. This allows Java to efficiently manage memory and object manipulation.
String PoolIn Java, Strings are one of the most commonly used reference types. Normally, objects are stored in the Heap, but to efficiently manage String objects, Java introduces a String Pool inside the Heap.
When a String is created using a string literal, it is directly stored in the String Pool. If the same string already exists, Java reuses the existing reference instead of creating a new object, saving memory.
When a String is created using the new keyword, it is first stored in the Heap as a separate object. Then, Java checks if the same value exists in the String Pool — if it does, the reference is linked to the existing string; otherwise, a new entry is added to the pool.
♦Image from GeeksforGeeksThis String Pool mechanism helps improve memory efficiency by avoiding duplicate String objects and reducing Heap usage.
In this explanation, I have mentioned String immutability. Now, let’s talk about what String immutability means…..
String immutabilityString immutability in Java means that once a String object is created, its value cannot be changed.
String str = "Hello";
str = str + " World";
In this example, the original string "Hello" remains unchanged. Instead, a new String object is created with the value "Hello World", and the variable str now references this new object. The original string "Hello" still exists in memory, demonstrating that String objects are immutable; modifications result in new objects rather than altering the existing ones. This immutability ensures thread safety and improves performance by allowing string literals to be reused from the String Pool.
To make Strings mutable, we can use StringBuilder and StringBuffer. In my previous article, I have explained about them.
Understanding StringBuffer and StringBuilder in Java.
In the context of String immutability and the String Pool, when a String object is created in Java, it first checks the String Pool to determine if an identical string already exists. If the string is found in the pool, the reference to the existing string is returned, and no new object is created. This reuse of references helps conserve memory.
String str1 = "Hello";
String str2 = "Hello";
Both str1 and str2 point to the same String object in the String Pool, illustrating immutability since neither can be modified. If you attempt to change the value of str1
str1 = str1 + " World";
A new String object is created with the value "Hello World", and str1 now references this new object, while the original "Hello" remains unchanged in the pool. This mechanism not only ensures that String objects are immutable but also optimizes memory usage by preventing duplicate string instances in the pool.
Okay, we’ve talked more about the JVM architecture, and there are many more aspects to discuss as well. But for a summary today, in this article, I have explained the JVM, JDK, JRE, as well as the main components in Java memory management, such as the heap, stack, reference types, and the String Pool. I hope you enjoyed this article and learned something new. For more, stay tuned with me. Good luck with your coding journey!
Goodbye, and best wishes on your coding adventures! 👋💻
♦How the JVM Manages Strings in Java. was originally published in Code Like A Girl on Medium, where people are continuing the conversation by highlighting and responding to this story.