Java Memory Allocation

Java memory allocation is a fundamental concept that determines where and how data is stored during program execution. Understanding memory allocation helps write efficient and bug-free code.

Memory Areas in Java

1. Stack Memory

Stack memory is used for static memory allocation and stores:

  • Primitive variables
  • Reference variables (addresses)
  • Method call information
  • Local variables

Characteristics:

  • LIFO (Last In, First Out) structure
  • Fast access
  • Limited size (typically 1-2MB per thread)
  • Automatically managed
1public void calculate() { 2 int x = 10; // Stack 3 String name = "Java"; // Reference on stack, object on heap 4 int[] numbers = new int[5]; // Reference on stack, array on heap 5 6 if (x > 5) { 7 int y = 20; // Stack (local to if block) 8 String temp = name; // Stack reference 9 } // y and temp popped from stack here 10} // x, name, numbers popped from stack here

2. Heap Memory

Heap memory is used for dynamic memory allocation and stores:

  • Objects
  • Arrays
  • Instance variables

Characteristics:

  • Larger than stack
  • Slower access than stack
  • Managed by garbage collector
  • Shared across all threads
1public class Example { 2 private int instanceVar; // Heap (part of object) 3 4 public void method() { 5 Example obj = new Example(); // Object on heap 6 int[] arr = new int[1000]; // Array on heap 7 } 8}

3. Method Area

Stores:

  • Class-level information
  • Static variables
  • Method bytecode
  • Constant pool
1public class Constants { 2 public static final int MAX_SIZE = 100; // Method area 3 private static String name = "Java"; // Method area 4 5 public static void staticMethod() { // Method bytecode in method area 6 // Method implementation 7 } 8}

4. Program Counter (PC Register

Stores:

  • Current instruction address
  • Thread-specific

Variable Allocation Examples

Primitive Variables

1public class PrimitiveAllocation { 2 public static void main(String[] args) { 3 // All primitives on stack 4 byte b = 10; // 1 byte on stack 5 short s = 20; // 2 bytes on stack 6 int i = 30; // 4 bytes on stack 7 long l = 40L; // 8 bytes on stack 8 float f = 50.5f; // 4 bytes on stack 9 double d = 60.6; // 8 bytes on stack 10 boolean bool = true; // 1 bit on stack 11 char c = 'A'; // 2 bytes on stack 12 } 13}

Reference Variables

1public class ReferenceAllocation { 2 public static void main(String[] args) { 3 // References on stack, objects on heap 4 String str = "Hello"; // Reference on stack, literal in pool 5 String obj = new String("World"); // Reference on stack, object on heap 6 7 int[] numbers = new int[5]; // Reference on stack, array on heap 8 Person person = new Person(); // Reference on stack, object on heap 9 } 10}

Object Memory Layout

1public class Person { 2 private String name; // Reference (4-8 bytes) 3 private int age; // Primitive (4 bytes) 4 private boolean active; // Primitive (1 byte) 5 // + Object header (typically 12-16 bytes) 6 // + Padding for alignment 7} 8 9// Memory usage: 10// Object header: ~12 bytes 11// Reference: 8 bytes (64-bit JVM) 12// int: 4 bytes 13// boolean: 1 byte 14// Padding: 3 bytes (for 8-byte alignment) 15// Total: ~28 bytes per Person object

Array Memory Allocation

One-Dimensional Arrays

1public class ArrayAllocation { 2 public static void main(String[] args) { 3 int[] numbers = new int[5]; 4 // Array object on heap: 20 bytes (5 * 4 bytes) 5 // Reference on stack: 8 bytes 6 7 String[] names = new String[3]; 8 // Array object on heap: 24 bytes (3 * 8 bytes for references) 9 // References initially null 10 11 names[0] = "Alice"; // String literal in string pool 12 names[1] = new String("Bob"); // String object on heap 13 names[2] = "Charlie"; // String literal in string pool 14 } 15}

Multi-Dimensional Arrays

1public class MultiArrayAllocation { 2 public static void main(String[] args) { 3 int[][] matrix = new int[3][4]; 4 // Outer array on heap: 24 bytes (3 * 8 bytes for references) 5 // 3 inner arrays on heap: 3 * 16 bytes (4 * 4 bytes each) 6 // Reference on stack: 8 bytes 7 8 // Ragged arrays 9 int[][] ragged = new int[3][]; 10 ragged[0] = new int[2]; // 8 bytes 11 ragged[1] = new int[4]; // 16 bytes 12 ragged[2] = new int[3]; // 12 bytes 13 } 14}

String Memory Allocation

String Literal Pool

1public class StringAllocation { 2 public static void main(String[] args) { 3 String s1 = "Hello"; // String pool 4 String s2 = "Hello"; // Same object as s1 5 String s3 = new String("Hello"); // Heap 6 7 System.out.println(s1 == s2); // true (same object) 8 System.out.println(s1 == s3); // false (different objects) 9 } 10}

String Concatenation

1public class StringConcatenation { 2 public static void main(String[] args) { 3 // Compile-time concatenation 4 String s1 = "Hello" + " World"; // Single string in pool 5 6 // Runtime concatenation 7 String s2 = "Hello"; 8 String s3 = s2 + " World"; // Creates new StringBuilder object 9 10 // Efficient concatenation in loops 11 StringBuilder sb = new StringBuilder(); 12 for (int i = 0; i < 100; i++) { 13 sb.append("Item ").append(i).append("\n"); 14 } 15 String result = sb.toString(); 16 } 17}

Garbage Collection

When Objects Become Eligible for GC

1public class GarbageCollection { 2 public static void method1() { 3 Person person = new Person("Alice"); 4 person = null; // Eligible for GC 5 } 6 7 public static void method2() { 8 Person person1 = new Person("Alice"); 9 Person person2 = new Person("Bob"); 10 person1 = person2; // Original person1 object eligible for GC 11 } 12 13 public static void method3() { 14 createPerson(); // Person object eligible for GC after method returns 15 } 16 17 private static Person createPerson() { 18 return new Person("Alice"); 19 } 20}

Memory Leaks

1public class MemoryLeaks { 2 private static List<Person> cache = new ArrayList<>(); 3 4 // Memory leak: Objects never removed 5 public void addToCache(Person person) { 6 cache.add(person); // Objects remain in cache forever 7 } 8 9 // Fixed: Use weak references or explicit cleanup 10 public void properCacheManagement() { 11 // Use WeakReference for automatic cleanup 12 Map<String, WeakReference<Person>> weakCache = new HashMap<>(); 13 14 // Or implement explicit cleanup 15 if (cache.size() > MAX_CACHE_SIZE) { 16 cache.clear(); // Explicit cleanup 17 } 18 } 19}

Memory Optimization Techniques

Object Pooling

1public class ObjectPool { 2 private static final List<Person> pool = new ArrayList<>(); 3 4 public static Person getPerson() { 5 if (pool.isEmpty()) { 6 return new Person(); 7 } 8 return pool.remove(pool.size() - 1); 9 } 10 11 public static void returnPerson(Person person) { 12 person.reset(); // Reset state 13 pool.add(person); 14 } 15}

Primitive vs Object Choice

1public class Optimization { 2 // Good: Use primitives when possible 3 private int count; // 4 bytes 4 private double price; // 8 bytes 5 6 // Avoid: Unnecessary object creation 7 private Integer countObj; // Reference + object overhead 8 9 // Use primitive collections for large datasets 10 private IntArrayList intList; // Specialized primitive collection 11 private ArrayList<Integer> integerList; // Object overhead 12}

Memory Analysis Tools

JVM Memory Flags

1# Heap size settings 2-Xms512m # Initial heap size 3-Xmx2g # Maximum heap size 4-XX:NewSize=128m # Young generation size 5-XX:MaxNewSize=512m # Maximum young generation size 6 7# Garbage collection 8-XX:+UseG1GC # Use G1 garbage collector 9-XX:+PrintGC # Print GC information 10-XX:+PrintGCDetails # Detailed GC information

Memory Profiling

1public class MemoryProfiling { 2 public static void main(String[] args) { 3 Runtime runtime = Runtime.getRuntime(); 4 5 long totalMemory = runtime.totalMemory(); 6 long freeMemory = runtime.freeMemory(); 7 long usedMemory = totalMemory - freeMemory; 8 long maxMemory = runtime.maxMemory(); 9 10 System.out.println("Used Memory: " + (usedMemory / 1024 / 1024) + " MB"); 11 System.out.println("Free Memory: " + (freeMemory / 1024 / 1024) + " MB"); 12 System.out.println("Total Memory: " + (totalMemory / 1024 / 1024) + " MB"); 13 System.out.println("Max Memory: " + (maxMemory / 1024 / 1024) + " MB"); 14 } 15}

Best Practices

  1. Use primitives when possible to avoid object overhead
  2. Initialize objects properly to avoid null references
  3. Be aware of string concatenation in loops
  4. Use appropriate collections based on usage patterns
  5. Implement proper cleanup for resources
  6. Monitor memory usage in production
  7. Use object pooling for frequently created/destroyed objects

Common Memory Issues

  • OutOfMemoryError: Heap exhausted
  • StackOverflowError: Stack space exhausted
  • Memory leaks: Objects not garbage collected
  • Excessive object creation: Performance degradation
  • Large arrays: Memory fragmentation

Understanding Java memory allocation is essential for writing efficient, scalable applications and troubleshooting memory-related issues.