Core Java · Lesson 2
Java Virtual Machine (JVM)
What is the Java Virtual Machine (JVM)?
The Java Virtual Machine (JVM) is an abstract computer that runs Java bytecode, making Java programs platform-independent. Instead of compiling directly to machine code, Java compiles to bytecode that any JVM can execute. This "write once, run anywhere" capability is one of Java's greatest strengths — the same compiled program runs on Windows, macOS, Linux, and more without recompilation.
How Java Code Gets to the JVM
Java's compilation process has two stages: first, the Java compiler (javac) converts your .java source files into .class files containing bytecode, an intermediate language. Then, the JVM interprets and executes this bytecode. This is fundamentally different from languages like C, which compile directly to platform-specific machine code. The JVM acts as a translator between portable bytecode and the underlying hardware.
// Compilation: javac HelloJVM.java → creates HelloJVM.class (bytecode)
// Execution: java HelloJVM → JVM reads and runs the bytecode
public class HelloJVM {
public static void main(String[] args) {
System.out.println("Hello from the JVM!");
int result = 5 + 3;
System.out.println("5 + 3 = " + result);
}
}
The JVM Memory Model
The JVM organizes memory into several regions to manage different types of data. The Heap stores objects and is garbage-collected automatically, while the Stack holds method calls and primitive variables. Understanding this separation helps you write efficient Java code and debug memory issues. Other regions include the Method Area (class structures) and Program Counter (current instruction).
public class MemoryExample {
public static void main(String[] args) {
// Stack: primitive variables and method references
int age = 25; // stored on stack
String name = "Alice"; // reference on stack, object on heap
// Heap: the String object "Alice"
Person person = new Person("Bob", 30); // person ref on stack, object on heap
System.out.println(name + " is " + age);
System.out.println(person.getName());
}
}
class Person {
private String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
String getName() {
return name;
}
}
Garbage Collection: Automatic Memory Management
Garbage Collection (GC) is a background process that automatically removes unused objects from the heap, freeing memory. Unlike C++ where you manually manage memory, Java handles this for you. The JVM periodically identifies objects with no references and deletes them. While this convenience is powerful, understanding GC helps you write code that doesn't create unnecessary objects.
public class GarbageCollectionDemo {
public static void main(String[] args) {
// Create objects
Person person1 = new Person("Charlie", 28);
Person person2 = new Person("Diana", 26);
// Reassign reference → original object is now garbage
person1 = person2;
// The object "Charlie, 28" is no longer referenced
// JVM's garbage collector will remove it from heap automatically
System.out.println("Active reference: " + person1.getName());
}
}
class Person {
private String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
String getName() {
return name;
}
}
Common JVM Configurations and Optimization
Different JVMs exist for different purposes: OpenJDK is the free, open-source standard; Oracle JDK is the commercial version with extra features; GraalVM offers high performance and polyglot capabilities. You can tune the JVM's behavior with flags like -Xmx (max heap size) and -Xms (initial heap size). For example, java -Xmx1024m MyApp gives your application up to 1 GB of heap memory.
// Command line: java -Xmx512m -Xms256m PerformanceApp
// This runs PerformanceApp with 512 MB max heap and 256 MB initial heap
public class PerformanceApp {
public static void main(String[] args) {
long maxMemory = Runtime.getRuntime().maxMemory() / (1024 * 1024);
long totalMemory = Runtime.getRuntime().totalMemory() / (1024 * 1024);
long freeMemory = Runtime.getRuntime().freeMemory() / (1024 * 1024);
System.out.println("Max Memory: " + maxMemory + " MB");
System.out.println("Total Memory: " + totalMemory + " MB");
System.out.println("Free Memory: " + freeMemory + " MB");
}
}
Common Mistake: Ignoring OutOfMemoryError. If your application creates objects faster than garbage collection can clean them up, the JVM will run out of heap memory and crash. Profile your application and adjust
-Xmxif needed, but also check for memory leaks where objects are kept in collections unnecessarily.
Best Practices for JVM-Aware Programming
- Minimize object creation in tight loops; reuse objects when possible
- Avoid circular references in custom data structures that prevent garbage collection
- Profile before optimizing; use tools like JVisualVM to see memory usage patterns
- Keep objects small and release references when done (set to
nullif the reference holder lives long) - Use StringBuilder instead of string concatenation in loops, which creates many temporary String objects
Real-World Example: Building a Scalable Web Service
A company building a high-traffic API service running on a distributed JVM cluster monitors garbage collection carefully. They configure the JVM with a larger heap (-Xmx4g) to handle thousands of user requests, and track GC pauses to ensure response times stay under 100ms. Understanding bytecode, memory regions, and GC behavior allows them to optimize performance, handle traffic spikes, and deploy reliably across multiple servers without recompilation.
Quiz Check