Skip to main content

JVM Internals & Garbage Collection

The Java Virtual Machine (JVM) is the cornerstone of Java’s platform independence. Understanding its internals is crucial for writing high-performance Java applications.

JVM Architecture

Overview

The JVM is a specification that defines how Java bytecode should be executed. Different vendors provide their own implementations (HotSpot, OpenJ9, GraalVM, etc.).

JVM Components

Responsible for loading class files into the JVM:Three phases:
  1. Loading: Reads .class files and creates binary data
  2. Linking: Verification, preparation, and resolution
  3. Initialization: Executes static initializers and static blocks
Class Loader Hierarchy:
Bootstrap ClassLoader (loads JDK classes)

Extension ClassLoader (loads extension libraries)

Application ClassLoader (loads application classes)
Memory areas used during program execution:
  • Shared among all threads
  • Stores objects and instance variables
  • Garbage collected
  • Divided into Young and Old generations
Executes bytecode through:
  • Interpreter: Executes bytecode line by line
  • JIT Compiler: Compiles frequently used bytecode to native code
  • Garbage Collector: Manages memory automatically

Java Bytecode

Understanding Bytecode

Java bytecode is the instruction set of the JVM. Understanding bytecode helps in performance optimization and debugging.
// Source code
public class HelloWorld {
    public static void main(String[] args) {
        int x = 5;
        int y = 10;
        int sum = x + y;
        System.out.println(sum);
    }
}
Corresponding bytecode:
Code:
  0: iconst_5       // Push constant 5
  1: istore_1       // Store in local variable 1 (x)
  2: bipush 10      // Push byte constant 10
  4: istore_2       // Store in local variable 2 (y)
  5: iload_1        // Load x
  6: iload_2        // Load y
  7: iadd           // Add x + y
  8: istore_3       // Store result in variable 3 (sum)
  9: getstatic #2   // Get System.out
  12: iload_3       // Load sum
  13: invokevirtual #3  // Call println
  16: return

Common Bytecode Instructions

iconst_<n>  : Push int constant n (-1 to 5)
iload_<n>   : Load int from local variable n
istore_<n>  : Store int to local variable n
dup         : Duplicate top stack value
pop         : Remove top stack value

Memory Management

Heap Structure

// Eden Space + 2 Survivor Spaces (S0, S1)
// New objects are allocated in Eden
// Minor GC moves live objects to survivor spaces

// Example: Object creation
String str = new String("Hello");  // Allocated in Eden
Integer num = 100;  // May be cached (Integer pool)

Memory Configuration

Proper JVM memory configuration is critical for application performance.
# Set heap size
java -Xms512m -Xmx2g MyApp
# -Xms: Initial heap size
# -Xmx: Maximum heap size

# Set Young Generation size
java -Xmn256m MyApp

# Set Metaspace size (Java 8+)
java -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m MyApp

# Enable GC logging
java -Xlog:gc*:file=gc.log:time,level,tags MyApp

Garbage Collection

GC Algorithms

Characteristics:
  • Single-threaded
  • Stop-the-world pauses
  • Suitable for small heaps
java -XX:+UseSerialGC MyApp
Use Case: Single-processor machines or small applications

GC Process

1

Marking Phase

Identifies live objects by following references from GC roots:
  • Thread stacks
  • Static variables
  • JNI references
2

Deletion Phase

Removes unreferenced objects and reclaims memory
3

Compaction (Optional)

Moves live objects together to prevent fragmentation

Monitoring GC

import java.lang.management.*;

public class GCMonitor {
    public static void main(String[] args) {
        // Get Memory MXBean
        MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
        
        // Heap memory usage
        MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
        System.out.println("Heap Memory:");
        System.out.println("  Init: " + heapUsage.getInit() / (1024 * 1024) + " MB");
        System.out.println("  Used: " + heapUsage.getUsed() / (1024 * 1024) + " MB");
        System.out.println("  Max: " + heapUsage.getMax() / (1024 * 1024) + " MB");
        
        // GC information
        for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
            System.out.println("\nGC: " + gc.getName());
            System.out.println("  Collections: " + gc.getCollectionCount());
            System.out.println("  Time: " + gc.getCollectionTime() + " ms");
        }
    }
}

Performance Tuning

JIT Compilation

The JIT compiler optimizes frequently executed code (“hot spots”) by compiling it to native machine code.
# JIT compiler options
java -XX:+TieredCompilation MyApp  # Default in Java 8+

# Print compilation
java -XX:+PrintCompilation MyApp

# Inline depth
java -XX:MaxInlineLevel=15 MyApp

# Code cache size
java -XX:ReservedCodeCacheSize=256m MyApp

Common Performance Issues

Common Causes:
  • Unclosed resources
  • Static collections
  • Thread locals
  • Event listeners not removed
Detection:
# Heap dump
jmap -dump:live,format=b,file=heap.bin <pid>

# Analyze with tools like:
# - Eclipse MAT
# - VisualVM
# - JProfiler
Symptoms:
  • Frequent GC cycles
  • Long pause times
  • Low throughput
Solutions:
  • Increase heap size
  • Tune GC algorithm
  • Reduce object creation
  • Use object pools for frequently created objects
Problems:
  • ClassNotFoundException
  • NoClassDefFoundError
  • Class version conflicts
Debugging:
# Verbose class loading
java -verbose:class MyApp

# Custom class loader debugging
java -XX:+TraceClassLoading -XX:+TraceClassUnloading MyApp

Profiling Tools

jstat

Monitor JVM statistics
jstat -gc <pid> 1000

jmap

Memory map and heap dump
jmap -heap <pid>

jstack

Thread dump for deadlock detection
jstack <pid>

VisualVM

All-in-one profiling tool with GUI

Best Practices

1

Choose Right GC

  • G1GC for most applications
  • ZGC/Shenandoah for low-latency requirements
  • Parallel GC for batch processing
2

Monitor and Profile

  • Enable GC logging in production
  • Use APM tools (New Relic, Dynatrace)
  • Regular heap dump analysis
3

Optimize Memory Usage

  • Reuse objects when possible
  • Use primitive types over wrappers
  • Close resources properly (try-with-resources)
4

Set Proper Limits

  • Configure heap size based on workload
  • Set Metaspace limits
  • Configure GC pause time goals

Java Fundamentals

Learn Java basics

Concurrency

Master multithreading

Spring Framework

Enterprise development

Build docs developers (and LLMs) love