Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Understanding the JVM Method Area: Structure, Evolution, and Memory Management

Tech Aug 10 19

Method Area

Interaction Between Stack, Heap, and Method Area

From the perspective of thread sharing

ThreadLocal ensures thread safety in concurrent environments. Typical use cases include database connection management and session management.

Object Access and Location

  1. Person class's .class information is stored in the method area
  2. The person variable is stored in the local variable table of the Java stack
  3. The actual person object is stored in the Java heap
  4. Within the person object, a pointer references the Person type data in the method area, indicating this person object was created using the Person class from the method area

Understanding the Method Area

Official Documentation: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.4

Where is the Method Area?

  1. The Java Virtual Machine Specification explicitly states that although the method area is logically part of the heap, some simple implementations may choose not to perform garbage collection or compression there. For HotSpot JVM, the method area has an alias called "Non-Heap," intended to separate it from the heap.
  2. Therefore, the method area can be considered independent memory separate from the Java heap.

Basic Understanding of the Method Area

The method area primarily stores Class information, while the heap primarily stores instantiated objects

  1. The method area, like the Java heap, is a shared memory region among threads. When multiple threads load the same class simultaneously, only one thread can load it while others wait until loading completes.
  2. The method area is created when the JVM starts, and its physical memory space, like the heap, can be discontinuous.
  3. The method area size, like heap space, can be fixed or expandable.
  4. The method area size determines how many classes the system can store. Loading too many classes causes an OutOfMemoryError: PermGen space or OutOfMemoryError: Metaspace.
    • Loading excessive third-party JAR packages
    • Deploying too many applications in Tomcat (30-50+)
    • Dynamically generating numerous reflection classes
  5. Closing the JVM releases this memory region.

Code Example

public class MethodAreaDemo {
    public static void main(String[] args) {
        System.out.println("start...");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("end...");
    }
}

This simple program loads over 1600 classes.

HotSpot Method Area Evolution

  1. Prior to JDK7, the method area was commonly called "Permanent Generation" (PermGen). JDK8 replaced PermGen with Metaspace. Think of the method area as an interface and PermGen/Metaspace as concrete implementation classes.
  2. Essentially, the method area and PermGen are not equivalent—they are only equivalent in HotSpot's implementation. The Java Virtual Machine Specification does not mandate a unified implementation approach. BEA JRockit and IBM J9, for example, do not have a PermGen concept.
  3. JDK8 completely abandoned the PermGen concept, replacing it with Metaspace implemented in native memory, similar to JRockit and J9.
  4. Metaspace is conceptually similar to PermGen as an implementation of the method area in the JVM specification. The key difference: Metaspace uses native memory rather than JVM-controlled heap space.
  5. PermGen and Metaspace differ not only in name—their internal structures were also adjusted.
  6. According to the Java Virtual Machine Specification, an OOM exception is thrown when the method area cannot satisfy new memory allocation requirements.

Configuring Method Area Size and OOM

The method area size is not fixed; the JVM can dynamically adjust it based on application needs.

JDK7 and Earlier (PermGen)

  1. Use -XX:PermSize to set the initial PermGen allocation space (default: 20.75M)
  2. Use -XX:MaxPermsize to set the maximum PermGen allocation space (32-bit default: 64M, 64-bit default: 82M)
  3. When loaded class information exceeds this value, an OutOfMemoryError: PermGen space exception occurs.

JDK8 and Later (Metaspace)

Configuring Metaspace Size

  1. Metaspace size can be configured using -XX:MetaspaceSize and -XX:MaxMetaspaceSize
  2. Defaults depend on the platform. On Windows, -XX:MetaspaceSize is approximately 21M, and -XX:MaxMetaspaceSize defaults to -1 (unlimited).
  3. Unlike PermGen, if size is not specified, the virtual machine consumes all available system memory. If metaspace overflow occurs, the VM throws OutOfMemoryError: Metaspace.
  4. -XX:MetaspaceSize sets the initial metaspace size. For a 64-bit server JVM, the default -XX:MetaspaceSize is 21MB—the initial high-water mark. Once this threshold is reached, Full GC triggers and unloads unused classes (those whose class loaders are no longer alive). The high-water mark then resets based on how much metaspace was freed. If insufficient space was freed (not exceeding MaxMetaspaceSize), the threshold increases. If too much space was freed, the threshold decreases.
  5. Setting the initial high-water mark too low causes frequent GC cycles. Monitor Full GC calls in garbage collector logs. To avoid frequent GC, set -XX:MetaspaceSize to a relatively high value.

Method Area OOM

Example

The following OOMTest class extends ClassLoader to access the defineClass() method for custom class loading:

public class OOMTest extends ClassLoader {
    public static void main(String[] args) {
        int loadedCount = 0;
        try {
            OOMTest loader = new OOMTest();
            for (int i = 0; i < 10000; i++) {
                ClassWriter writer = new ClassWriter(0);
                writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "DynamicClass" + i, null, "java/lang/Object", null);
                byte[] bytecode = writer.toByteArray();
                loader.defineClass("DynamicClass" + i, bytecode, 0, bytecode.length);
                loadedCount++;
            }
        } finally {
            System.out.println(loadedCount);
        }
    }
}

Without Metaspace Limit

Using default JVM parameters (no metaspace upper limit):

10000

With Metaspace Limit

JVM parameters: -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m

Output:

8531
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
    at com.example.OOMTest.main(OOMTest.java:29)

Resolving OOM Errors

  1. To resolve OOM or heap space exceptions, use memory analysis tools (like Eclipse Memory Analyzer) to analyze heap dumps. Confirm whether objects in memory are necessary—distinguish between Memory Leak and Memory Overflow.
  2. Memory Leak occurs when numerous references point to objects that will no longer be used but remain reachable from GC Roots, preventing their collection.
  3. For memory leaks, use tools to trace the reference chain from leaked objects to GC Roots. This reveals the code paths causing the leak. Understanding the leak object's type and its GC Root reference chain helps pinpoint the problematic code location.
  4. If no memory leak exists (objects in memory are genuinely needed), examine JVM heap parameters (-Xmx and -Xms) against physical memory to determine if heap size can be increased. Also review code for objects with excessively long lifecycles or holding state for extended periods, aiming to reduce runtime memory consumption.

Internal Structure of the Method Area

What is Stored in the Method Area?

Concept

According to "Deep Understanding of the Java Virtual Machine," the method area stores: type information loaded by the VM, constants, static variables, and JIT compiled code cache.

Type Information

For each loaded type (class, interface, enum, annotation), the JVM must store the following in the method area:

  1. The type's fully qualified name (package.name format)
  2. The type's direct parent class's fully qualified name (interfaces and java.lang.Object have no parent)
  3. The type's modifiers (public, abstract, final subset)
  4. An ordered list of the type's direct interfaces

Field Information

Field information corresponds to member variables:

  1. The JVM must store all field-related information and declaration order.
  2. Field information includes: field name, field type, and field modifiers (public, private, protected, static, final, volatile, transient subset).

Method Information

The JVM must store the following for all methods, including declaration order:

  1. Method name
  2. Method return type (including void), where void maps to void.class in Java
  3. Method parameter count and types (in order)
  4. Method modifiers (public, private, protected, static, final, synchronized, native, abstract subset)
  5. Method bytecode, operand stack size, local variable table size (except for abstract and native methods)
  6. Exception table (except for abstract and native methods), recording each exception handler's start position, end position, program counter offset, and caught exception class constant pool index

Example

public class MethodInnerStrucTest extends Object implements Comparable<String>, Serializable {
    public int num = 10;
    private static String str = "Testing method internals";
    
    public void test1() {
        int count = 20;
        System.out.println("count = " + count);
    }
    
    public static int test2(int divisor) {
        int result = 0;
        try {
            int value = 30;
            result = value / divisor;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    @Override
    public int compareTo(String o) {
        return 0;
    }
}

Disassemble the bytecode: javap -v -p MethodInnerStrucTest.class > test.txt

Bytecode output:

Classfile /path/to/MethodInnerStrucTest.class
  Last modified 2020-11-13; size 1626 bytes
  MD5 checksum 0d0fcb54854d4ce183063df985141ad0
  Compiled from "MethodInnerStrucTest.java"

public class com.example.MethodInnerStrucTest extends java.lang.Object implements java.lang.Comparable<java.lang.String>, java.io.Serializable
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #18.#52        // java/lang/Object."<init>":()V
   #2 = Fieldref           #17.#53        // com/example/MethodInnerStrucTest.num:I
   #3 = Fieldref           #54.#55        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Class              #56            // java/lang/StringBuilder
   #5 = Methodref          #4.#52         // java/lang/StringBuilder."<init>":()V
   #6 = String             #57            // count =
   #7 = Methodref          #4.#58         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #4.#59         // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   #9 = Methodref          #4.#60         // java/lang/StringBuilder.toString:()V
  #10 = Methodref          #61.#62        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #11 = Class              #63            // java/lang/Exception
  #12 = Methodref          #11.#64        // java/lang/Exception.printStackTrace:()V
  #13 = Class              #65            // java/lang/String
  #14 = Methodref          #17.#66        // com/example/MethodInnerStrucTest.compareTo:(Ljava/lang/String;)I
  #15 = String             #67            // Testing method internals
  #16 = Fieldref           #17.#68        // com/example/MethodInnerStrucTest.str:Ljava/lang/String;
  #17 = Class              #69            // com/example/MethodInnerStrucTest
  #18 = Class              #70            // java/lang/Object
  #19 = Class              #71            // java/lang/Comparable
  #20 = Class              #72            // java/io/Serializable
  #21 = Utf8               num
  #22 = Utf8               I
  #23 = Utf8               str
  #24 = Utf8               Ljava/lang/String;
  #25 = Utf8               <init>
  #26 = Utf8               ()V
  #27 = Utf8               Code
  #28 = Utf8               LineNumberTable
  #29 = Utf8               LocalVariableTable
  #30 = Utf8               this
  #31 = Utf8               Lcom/example/MethodInnerStrucTest;
  #32 = Utf8               test1
  #33 = Utf8               count
  #34 = Utf8               test2
  #35 = Utf8               (I)I
  #36 = Utf8               value
  #37 = Utf8               e
  #38 = Utf8               Ljava/lang/Exception;
  #39 = Utf8               cal
  #40 = Utf8               result
  #41 = Utf8               StackMapTable
  #42 = Class              #63            // java/lang/Exception
  #43 = Utf8               compareTo
  #44 = Utf8               (Ljava/lang/String;)I
  #45 = Utf8               o
  #46 = Utf8               (Ljava/lang/Object;)I
  #47 = Utf8               <clinit>
  #48 = Utf8               Signature
  #49 = Utf8               Ljava/lang/Object;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/io/Serializable;
  #50 = Utf8               SourceFile
  #51 = Utf8               MethodInnerStrucTest.java
  #52 = NameAndType        #25:#26        // "<init>":()V
  #53 = NameAndType        #21:#22        // num:I
  #54 = Class              #73            // java/lang/System
  #55 = NameAndType        #74:#75        // out:Ljava/io/PrintStream;
  #56 = Utf8               java/lang/StringBuilder
  #57 = Utf8               count =
  #58 = NameAndType        #76:#77        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #59 = NameAndType        #76:#78        // append:(I)Ljava/lang/StringBuilder;
  #60 = NameAndType        #79:#80        // toString:()V
  #61 = Class              #81            // java/io/PrintStream
  #62 = NameAndType        #82.#83        // println:(Ljava/lang/String;)V
  #63 = Utf8               java/lang/Exception
  #64 = NameAndType        #84:#26        // printStackTrace:()V
  #65 = Utf8               java/lang/String
  #66 = NameAndType        #43:#44        // compareTo:(Ljava/lang/String;)I
  #67 = Utf8               Testing method internals
  #68 = NameAndType        #23:#24        // str:Ljava/lang/String;
  #69 = Utf8               com/example/MethodInnerStrucTest
  #70 = Utf8               java/lang/Object
  #71 = Utf8               java/lang/Comparable
  #72 = Utf8               java/io/Serializable
  #73 = Utf8               java/lang/System
  #74 = Utf8               out
  #75 = Utf8               Ljava/io/PrintStream;
  #76 = Utf8               append
  #77 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #78 = Utf8               (I)Ljava/lang/StringBuilder;
  #79 = Utf8               toString
  #80 = Utf8               ()V
  #81 = Utf8               java/io/PrintStream
  #82 = Utf8               println
  #83 = Utf8               (Ljava/lang/String;)V
  #84 = Utf8               printStackTrace
{
  // Field information
  public int num;
    descriptor: I
    flags: ACC_PUBLIC

  private static java.lang.String str;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE, ACC_STATIC

  // Method information
  public com.example.MethodInnerStrucTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: bipush        10
         7: putfield      #2                  // Field num:I
        10: return
      LineNumberTable:
        line 10: 0
        line 12: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  this   Lcom/example/MethodInnerStrucTest;

  public void test1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=2, args_size=1
         0: bipush        20
         2: istore_1
         3: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         6: new           #4                  // class java/lang/StringBuilder
         9: dup
        10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
        13: ldc           #6                  // String count =
        15: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        18: iload_1
        19: invokevirtual #8                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        22: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()V
        25: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        28: return
      LineNumberTable:
        line 17: 0
        line 18: 3
        line 19: 28
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      29     0  this   Lcom/example/MethodInnerStrucTest;
            3      26     1 count   I

  public static int test2(int);
    descriptor: (I)I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: bipush        30
         4: istore_2
         5: iload_2
         6: iload_0
         7: idiv
         8: istore_1
         9: goto          17
        12: astore_2
        13: aload_2
        14: invokevirtual #12                 // Method java/lang/Exception.printStackTrace:()V
        17: iload_1
        18: ireturn
      Exception table:
         from    to  target type
             2     9    12   Class java/lang/Exception
      LineNumberTable:
        line 21: 0
        line 23: 2
        line 24: 5
        line 27: 9
        line 25: 12
        line 26: 13
        line 28: 17
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            5       4     2 value   I
           13       4     2     e   Ljava/lang/Exception;
            0      19     0   cal   I
            2      17     1 result   I
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ int, int ]
          stack = [ class java/lang/Exception ]
        frame_type = 4 /* same */

  public int compareTo(java.lang.String);
    descriptor: (Ljava/lang/String;)I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: iconst_0
         1: ireturn
      LineNumberTable:
        line 33: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       2     0  this   Lcom/example/MethodInnerStrucTest;
            0       2     1     o   I

  public int compareTo(java.lang.Object);
    descriptor: (Ljava/lang/Object;)I
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: checkcast     #13                 // class java/lang/String
         5: invokevirtual #14                 // Method compareTo:(Ljava/lang/String;)I
         8: ireturn
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/example/MethodInnerStrucTest;

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #15                 // String Testing method internals
         2: putstatic     #16                 // Field str:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 13: 0
}
Signature: #49                          // Ljava/lang/Object;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/io/Serializable;
SourceFile: "MethodInnerStrucTest.java"

Type Information

In the runtime method area, class information records which class loader loaded the class, and the class loader also tracks which classes it has loaded.

Field Information

  1. descriptor: I indicates the field type is Integer
  2. flags: ACC_PUBLIC indicates the field modifier is public

Method Information

  1. descriptor: ()V indicates the method return type is void
  2. flags: ACC_PUBLIC indicates the method modifier is public
  3. stack=3 indicates the operand stack depth is 3
  4. locals=2 indicates there are 2 local variables (instance methods include this)
  5. The test1() method has no parameters, but its args_size=1 because this is passed as a parameter

Non-Final Class Variables

  1. Static variables are associated with classes and load with them, becoming part of class data logically
  2. Class variables are shared among all instances, accessible even without class instances

Example

public class MethodAreaTest {
    public static void main(String[] args) {
        Order order = null;
        order.greet();
        System.out.println(order.count);
    }
}

class Order {
    public static int count = 1;
    public static final int number = 2;

    public static void greet() {
        System.out.println("hello!");
    }
}

Output:

hello!
1

Even setting order to null causes no NullPointerException, further demonstrating that static fields and methods load with the class and do not belong to specific instances.

Global Constants: static final

  1. Global constants are declared with static final
  2. Each global constant is allocated at compile time

Comparing bytecode for the two fields:

class Order {
    public static int count = 1;
    public static final int number = 2;
}

Generated bytecode:

public static int count;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC

  public static final int number;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 2

The number field with both static and final has its value hardcoded into the bytecode file at compile time.

Runtime Constant Pool

Runtime Constant Pool vs. Constant Pool

Official Documentation: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

  1. The method area internally contains the runtime constant pool
  2. Bytecode files internally contain the constant pool (visible in the bytecode files shown earlier)
  3. Understanding the method area requires understanding ClassFile structure, as loaded class information resides there
  4. Understanding the runtime constant pool requires understanding the constant pool table within ClassFile

Constant Pool

  1. A valid bytecode file contains not only class version information, fields, methods, and interface descriptors but also a Constant Pool Table including various literal values and symbolic references to types, fields, and methods
  2. Literals: values like 10, "hello" are literals

Why is a Constant Pool Needed?

  1. A Java source file compiles into one bytecode file. Bytecode needs data support, often too large to store directly in bytecode. Instead, it stores references to the constant pool. This supports dynamic linking, as explained earlier

Example code:

public class SimpleClass {
    public void sayHello() {
        System.out.println("hello");
    }
}

Although this code is only 194 bytes, it uses String, System, PrintStream, and Object structures. If six places use the "hello" string without a constant pool, the string would need repeating six times, causing bloat. Instead, structure information needed for execution is recorded in the constant pool and loaded via references. With more complex code, more structures are referenced, making the constant pool essential.

Constant Pool Contents

  1. Numeric values
  2. String values
  3. Class references
  4. Field references
  5. Method references

Method bytecode from test1():

 0 bipush 20
 2 istore_1
 3 getstatic #3 <java/lang/System.out>
 6 new #4 <java/lang/StringBuilder>
 9 dup
10 invokespecial #5 <java/lang/StringBuilder.<init>>
13 ldc #6 <count = >
15 invokevirtual #7 <java/lang/StringBuilder.append>
18 iload_1
19 invokevirtual #8 <java/lang/StringBuilder.append>
22 invokevirtual #9 <java/lang/StringBuilder.toString>
25 invokevirtual #10 <java/io/PrintStream.println>
28 return

References like #3, #5 with # symbols reference the constant pool.

Constant Pool Summary

The constant pool is essentially a lookup table. Virtual machine instructions use this table to find class names, method names, parameter types, and literals.

Runtime Constant Pool

  1. The runtime constant pool is part of the method area
  2. The constant pool table is part of the Class bytecode file, storing literal values and symbolic references generated at compile time. These contents are moved to the method area's runtime constant pool when classes are loaded (the runtime constant pool is the runtime name for the constant pool)
  3. Upon loading classes and interfaces into the VM, the corresponding runtime constant pool is created
  4. The JVM maintains a constant pool for each loaded type (class or interface). Pool data is accessed via indices like array elements
  5. The runtime constant pool contains various constants, including numeric literals resolved at compile time and method/field references obtained through runtime resolution. At this point, symbolic addresses are replaced with actual addresses

The runtime constant pool differs from the Class file constant pool in that it exhibits dynamic characteristics.

  1. The runtime constant pool resembles the symbol table in traditional programming languages but contains richer data
  2. When creating the runtime constant pool for a class or interface, if required memory exceeds the method area's maximum, the JVM throws an OutOfMemoryError

Method Area Usage Example

Source code:

public class MethodAreaDemo {
    public static void main(String[] args) {
        int x = 500;
        int y = 100;
        int a = x / y;
        int b = 50;
        System.out.println(a + b);
    }
}

Bytecode:

public class com.example.MethodAreaDemo
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#24         // java/lang/Object."<init>":()V
   #2 = Fieldref           #25.#26        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #27.#28        // java/io/PrintStream.println:(I)V
   #4 = Class              #29            // com/example/MethodAreaDemo
   #5 = Class              #30            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               LocalVariableTable
  #11 = Utf8               this
  #12 = Utf8               Lcom/example/MethodAreaDemo;
  #13 = Utf8               main
  #14 = Utf8               ([Ljava/lang/String;)V
  #15 = Utf8               args
  #16 = Utf8               [Ljava/lang/String;
  #17 = Utf8               x
  #18 = Utf8               I
  #19 = Utf8               y
  #20 = Utf8               a
  #21 = Utf8               b
  #22 = Utf8               SourceFile
  #23 = Utf8               MethodAreaDemo.java
  #24 = NameAndType        #6:#7          // "<init>":()V
  #25 = Class              #31            // java/lang/System
  #26 = NameAndType        #32:#33        // out:Ljava/io/PrintStream;
  #27 = Class              #34            // java/io/PrintStream
  #28 = NameAndType        #35.#36        // println:(I)V
  #29 = Utf8               com/example/MethodAreaDemo
  #30 = Utf8               java/lang/Object
  #31 = Utf8               java/lang/System
  #32 = Utf8               out
  #33 = Utf8               Ljava/io/PrintStream;
  #34 = Utf8               java/io/PrintStream
  #35 = Utf8               println
  #36 = Utf8               (I)V
{
  public com.example.MethodAreaDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/example/MethodAreaDemo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=5, args_size=1
         0: sipush        500
         3: istore_1
         4: bipush        100
         6: istore_2
         7: iload_1
         8: iload_2
         9: idiv
        10: istore_3
        11: bipush        50
        13: istore        4
        15: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        18: iload_3
        19: iload         4
        21: iadd
        22: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        25: return
      LineNumberTable:
        line 9: 0
        line 10: 4
        line 11: 7
        line 12: 11
        line 13: 15
        line 14: 25
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      26     0  args   [Ljava/lang/String;
            4      22     1     x   I
            7      19     2     y   I
           11      15     3     a   I
           15      11     4     b   I
}
SourceFile: "MethodAreaDemo.java"

Bytecode Instruction Execution Flow

  1. Initial state
  2. Push operand 500 onto the operand stack
  3. Pop 500 from the operand stack and store in local variable table at index 1 4-8. Subsequent operations follow similar patterns 9-10. Continue execution
  4. References to System class (corrected: #25 and #26) 12-13. Continue processing
  5. After addition operation, store result at operand stack top
  6. Actual print operation
  7. Return

Symbolic Reference to Direct Reference

When invoking System.out.println(), the VM first checks if the System class is loaded, then checks if PrintStream is loaded. If not loaded, it performs loading, converting symbolic references in the constant pool to direct references (actual memory addresses) in the runtime constant pool.

Method Area Evolution Details

PermGen Evolution

  1. Only HotSpot has PermGen. BEA JRockit and IBM J9 have no PermGen concept. Implementation details of the method area are not mandated by the Java Virtual Machine Specification.
  2. HotSpot method area changes:
Version Description
JDK1.6 and earlier Has PermGen; static variables stored on PermGen
JDK1.7 Has PermGen but being gradually "de-PermGen'd"; String constant pool and static variables moved to heap
JDK1.8 No PermGen; type information, fields, methods, and constants stored in native memory metaspace; String constant pool and static variables remain on heap

JDK6

Method area implemented via PermGen using JVM virtual memory.

JDK7

Method area implemented via PermGen using JVM virtual memory.

JDK8

Method area implemented via Metaspace using physical machine native memory.

Why Replace PermGen with Metaspace?

Official Documentation: http://openjdk.java.net/jeps/122

  1. With Java 8, HotSpot VM no longer has PermGen. However, class metadata was not eliminated—moved to native memory outside the heap called Metaspace.
  2. Since class metadata is allocated in native memory, metaspace's maximum size is the system's available memory.
  3. This change was necessary because:
    • Setting PermGen size is difficult. In scenarios with excessive dynamic class loading, PermGen OOM easily occurs. For example, in large web applications with many features requiring continuous dynamic class loading, fatal errors occur: Exception in thread 'dubbo client x.x connector' java.lang.OutOfMemoryError:PermGen space. The key difference: metaspace is not in JVM memory but uses native memory, so its size is only limited by available system memory.
    • Tuning PermGen is difficult. Method area garbage collection primarily recycles abandoned constants and unused types. The main tuning goal is reducing Full GC.
      • Some believe the method area (metaspace or PermGen in HotSpot) has no garbage collection. However, the Java Virtual Machine Specification allows not implementing garbage collection there. Some collectors (like ZGC in JDK11) do not support class unloading.
      • Generally,回收效果 is difficult to satisfy, especially type unloading with stringent conditions. But collection is sometimes necessary. Sun's bug list includes serious bugs caused by incomplete collection of this area in older HotSpot versions.

String Constant Pool

Why Adjust String Table Position?

JDK7 moved the StringTable to heap space because PermGen's collection efficiency is low—only triggered during Full GC when old generation or PermGen space is insufficient. This causes poor StringTable collection, yet many strings are created in development. Moving to the heap enables timely memory collection.

Where are Static Variables Stored?

public class StaticObjTest {
    static class Test {
        static ObjectHolder staticObj = new ObjectHolder();
        ObjectHolder instanceObj = new ObjectHolder();

        void foo() {
            ObjectHolder localObj = new ObjectHolder();
            System.out.println("done");
        }
    }
}

Note: This discusses staticObj reference variable storage, not the new ObjectHolder() object itself.

Where are Object Instances Stored?

Conclusion:

  1. Object instances referenced by static references (new byte[1024 * 1024 * 100]) always exist in heap space
  2. Only the variable itself (like arr below) changed location in JDK6, JDK7, and JDK8
public class StaticFieldTest {
    private static byte[] arr = new byte[1024 * 1024 * 100];//100MB

    public static void main(String[] args) {
        System.out.println(StaticFieldTest.arr);
    }
}

Under JDK6

staticObj is stored with Test's type information in the method area.

Under JDK7

staticObj's reference and object are both stored in the heap.

Under JDK8

staticObj's reference and object are both stored in the heap.

Where are Variable Names Stored?

Use JHSDB tool (available from JDK9 in the bin directory) for analysis.

public class StaticObjTest {
    static class Test {
        static ObjectHolder staticObj = new ObjectHolder();
        ObjectHolder instanceObj = new ObjectHolder();

        void foo() {
            ObjectHolder localObj = new ObjectHolder();
            System.out.println("done");
        }
    }

    private static class ObjectHolder {
    }

    public static void main(String[] args) {
        Test test = new StaticObjTest.Test();
        test.foo();
    }
}

Under JDK6

  1. staticObj is stored with Test's type information in the method area
  2. instanceObj is stored with Test's object instance in the Java heap
  3. localObj is stored in foo()'s stack frame local variable table
  4. Testing reveals all three objects' memory addresses fall within the Eden range, concluding: object instances are always allocated in the Java heap

From the Java Virtual Machine Specification's conceptual model, all Class-related information should be stored in the method area. However, the specification does not mandate implementation details, allowing virtual machines flexibility. Starting from JDK7, HotSpot chose to store static variables and Class objects in the Java heap together. Experiments confirm this.

Garbage Collection in the Method Area

  1. Some believe the method area (metaspace or PermGen in HotSpot) has no garbage collection. However, the Java Virtual Machine Specification permits not implementing it. Some collectors (like ZGC in JDK11) do not fully support type unloading.
  2. Generally, collection effectiveness is unsatisfactory, especially type unloading with stringent conditions. But collection is sometimes necessary. Sun's bug list includes serious bugs caused by incomplete collection in older HotSpot versions.
  3. Method area GC primarily collects two things: abandoned constants and unused types.
  4. The constant pool contains two main constant types: literals and symbolic references. Literals are closer to Java language-level constants like text strings and final-declared constant values. Symbolic references belong to compilation theory, including:
    • Fully qualified names of classes and interfaces
    • Field names and descriptors
    • Method names and descriptors
  5. HotSpot's constant pool collection strategy is clear: constants with no references from anywhere can be collected.
  6. Collecting abandoned constants is very similar to collecting heap objects (constant collection is relatively simple; type collection is the focus).

Type Unloading

Determining if a constant is "abandoned" is relatively simple, but determining if a type belongs to "no longer used" requires all three conditions:

  • All instances of the class have been collected (no class instances or subclass instances exist in the Java heap)
  • The class loader that loaded the class has been collected (unless in carefully designed replaceable class loader scenarios like OSGi, JSP reload, this is usually difficult to achieve)
  • The java.lang.Class object for the class has no references anywhere and cannot be accessed via reflection

The JVM permits collecting unused classes that satisfy these three conditions—collection is not mandatory like object collection. HotSpot provides -Xnoclassgc to control this, and -verbose:class, -XX:+TraceClassLoading, -XX:+TraceClassUnLoading to view class loading and unloading information.

In scenarios heavily using reflection, dynamic proxies, CGLib bytecode frameworks, dynamically generating JSPs, and OSGi with frequent custom class loaders, the JVM typically needs type unloading capability to prevent excessive memory pressure on the method area.

Runtime Data Area Summary

Object Instance

Object Creation

Creation Methods

  1. new: Most common, single instance calls to getInstance() static method, XXXFactory static method
  2. Class.newInstance: Deprecated in JDK9; only invokes no-arg constructor with public access
  3. Constructor.newInstance: Reflection approach, invokes no-arg or parameterized constructors
  4. clone(): Does not invoke any constructor; requires implementing Cloneable interface's clone method
  5. Deserialization: Binary stream from file or network; commonly used for socket network transmission
  6. Third-party library Objenesis

Object Creation Steps

Checking if the Class is Loaded

  1. Upon encountering a new instruction, the VM first checks if the instruction parameter can locate a class's symbolic reference in metaspace's constant pool, and whether the class has been loaded, resolved, and initialized
  2. If not loaded, the current class loader searches for the .class file using ClassLoader + package name + class name as the key under the parent delegation model. If not found, ClassNotFoundException is thrown; if found, class loading proceeds and a Class object is generated

Allocating Memory

  1. Calculate object size first, then allocate memory in the heap. If instance member variables are reference types, only allocate reference variable space (4 bytes)
  2. If memory is contiguous (Bump the Pointer): The VM uses this allocation method for compacting garbage collectors (Serial, ParNew). Used memory on one side, free memory on the other, with a pointer as boundary. Allocation moves the pointer by the object size
  3. If memory is fragmented (Free List): The VM maintains a list of available memory blocks. It finds a sufficiently large block for the object and updates the list. This method is chosen when the heap is fragmented due to non-compacting collectors (mark-sweep algorithms leave memory fragmentation)

Handling Concurrency

  1. CAS with retry on failure ensures atomicity
  2. Pre-allocate TLAB per thread via -XX:+UseTLAB (area locking mechanism)
  3. Each thread gets a region in Eden

Initializing Allocated Space

  • Set all properties to default values, ensuring object instance fields can be used without assignment
  • Property assignment order:
    1. Default value initialization
    2. Explicit initialization/code block initialization (parallel, order determined by code)
    3. Constructor initialization

Setting Object Header

Object's class metadata, hash code, GC information, and lock data are stored in the object header. Specific settings depend on JVM implementation.

Executing init Method

  1. From the Java program's perspective, initialization begins now. Initialize member variables, execute instance initialization blocks, invoke class constructor, and assign the object's heap address to the reference variable
  2. Generally (determined by invokespecial instruction following new), the init method executes after new to initialize the object according to developer intent, creating a truly usable object

init Method Bytecode

public class Customer {
    int id = 1001;
    String name;
    Account account;

    {
        name = "Anonymous Customer";
    }
    
    public Customer() {
        account = new Account();
    }
}

class Account {
}

Customer bytecode:

 0 aload_0
 1 invokespecial #1 <java/lang/Object.<init>>
 4 aload_0
 5 sipush 1001
 8 putfield #2 <com/example/Customer.id>
11 aload_0
12 ldc #3 <Anonymous Customer>
14 putfield #4 <com/example/Customer.name>
17 aload_0
18 new #5 <com/example/Account>
21 dup
22 invokespecial #6 <com/example/Account.<init>>
25 putfield #7 <com/example/Customer.account>
28 return

init() method bytecode:

  • Default initialization: id = 1001;
  • Explicit/block initialization: name = "Anonymous Customer";
  • Constructor initialization: account = new Account();

Object Memory Layout

public class Customer {
    int id = 1001;
    String name;
    Account account;

    {
        name = "Anonymous Customer";
    }
    
    public Customer() {
        account = new Account();
    }
    
    public static void main(String[] args) {
        Customer customer = new Customer();
    }
}

class Account {
}

Memory layout:

|---------------------------------------------------------------|
|                        Object Header (Header)                  |
|---------------------------------------------------------------|
|  Mark Word (64 bits) | Klass Pointer (32/64 bits on 32/64-bit VM) |
|---------------------------------------------------------------|
|                        Instance Data (Instance Data)           |
|---------------------------------------------------------------|
|  id: int (4 bytes)  |  name: String reference (4/8 bytes)        |
|  account: Account reference (4/8 bytes)                       |
|---------------------------------------------------------------|
|                      Alignment Padding (Padding)               |
|---------------------------------------------------------------|

Object Access

How does the JVM access objects through stack frame references?

Two object access methods: Handle and Direct Pointer

Handle Access

  1. Disadvantage: Allocates space in heap for handle pool; handles consume memory; two pointer traversals required to access heap objects, lower efficiency
  2. Advantage: reference stores stable handle address; when objects move (common during garbage collection), only the instance data pointer in the handle changes, reference itself remains unmodified

Direct Pointer (HotSpot)

  1. Advantage: Reference in local variable table points directly to heap instance; instance contains type pointer pointing to method area type data
  2. Disadvantage: When objects move (common during GC), reference values must be updated

Direct Memory

Direct Memory Overview

  1. Not part of the JVM runtime data area or memory region defined by the Java Virtual Machine Specification
  2. Native memory outside the Java heap, directly requested from the system
  3. Originates from NIO, operating native memory through heap-based DirectByteBuffer
  4. Direct memory access is typically faster than Java heap—higher read/write performance
  5. For performance-critical, frequently read/write scenarios, consider using direct memory
  6. Java NIO allows Java programs to use direct memory for data buffers
public class BufferTest {
    private static final int BUFFER = 1024 * 1024 * 1024;//1GB

    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER);
        System.out.println("Direct memory allocated, request confirmed!");

        Scanner scanner = new Scanner(System.in);
        scanner.next();

        System.out.println("Direct memory releasing!");
        buffer = null;
        System.gc();
        scanner.next();
    }
}

Directly consumes 1GB of native memory.

BIO vs NIO

Non-Direct Buffer (BIO)

Traditional BIO architecture requires user-mode to kernel-mode transitions when reading/writing local files.

Direct Buffer (NIO)

NIO operates directly on physical disk, eliminating intermediate steps.

Direct Memory and OOM

  1. Direct memory can also cause OutOfMemoryError
  2. Direct memory is outside the Java heap, so its size is not directly limited by -Xmx maximum heap size. However, system memory is finite—the total of Java heap and direct memory is still limited by OS-available memory
  3. Direct memory disadvantages:
    • Higher allocation/recycling cost
    • Not managed by JVM garbage collection
  4. Direct memory size can be configured via MaxDirectMemorySize
  5. If unspecified, defaults match the -Xmx heap maximum value
public class BufferTest2 {
    private static final int BUFFER = 1024 * 1024 * 20;//20MB

    public static void main(String[] args) {
        List<ByteBuffer> buffers = new ArrayList<>();

        int count = 0;
        try {
            while (true) {
                ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER);
                buffers.add(buffer);
                count++;
                Thread.sleep(100);
            }
        } finally {
            System.out.println(count);
        }
    }
}
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
    at java.nio.Bits.reserveMemory(Bits.java:694)
    at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
    at com.example.BufferTest2.main(BufferTest2.java:21)

Common Interview Questions

  1. Baidu:
    • Third round: How is an object stored in the JVM? What does the object header contain?
  2. Ant Group:
    • Second round: What is inside the Java object header?
  3. Xiaomi:
    • JVM memory partitioning; why have young and old generations?
  4. ByteDance:
    • Second round: Java memory areas
    • Second round: VM runtime data area
    • When do objects enter the old generation?
  5. JD.com:
    • JVM memory structure, Eden and Survivor ratio
    • Why divide JVM memory into young, old, and permanent generations? Why divide young generation into Eden and Survivor?
  6. Tmall:
    • First round: JVM memory model and areas, details for each area's purpose
    • First round: JVM memory model changes in Java 8
  7. Pinduoduo:
    • JVM memory partitioning and each area's function?
  8. Meituan:
    • Java memory allocation
    • Does garbage collection occur in JVM PermGen?
    • First round: JVM memory partitioning, why have young and old generations?

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.