Understanding the JVM Method Area: Structure, Evolution, and Memory Management
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
- Person class's .class information is stored in the method area
- The person variable is stored in the local variable table of the Java stack
- The actual person object is stored in the Java heap
- 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?
- 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.
- 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
- 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.
- The method area is created when the JVM starts, and its physical memory space, like the heap, can be discontinuous.
- The method area size, like heap space, can be fixed or expandable.
- 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
- 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
- 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.
- 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.
- JDK8 completely abandoned the PermGen concept, replacing it with Metaspace implemented in native memory, similar to JRockit and J9.
- 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.
- PermGen and Metaspace differ not only in name—their internal structures were also adjusted.
- 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)
- Use
-XX:PermSizeto set the initial PermGen allocation space (default: 20.75M) - Use
-XX:MaxPermsizeto set the maximum PermGen allocation space (32-bit default: 64M, 64-bit default: 82M) - When loaded class information exceeds this value, an OutOfMemoryError: PermGen space exception occurs.
JDK8 and Later (Metaspace)
Configuring Metaspace Size
- Metaspace size can be configured using
-XX:MetaspaceSizeand-XX:MaxMetaspaceSize - Defaults depend on the platform. On Windows,
-XX:MetaspaceSizeis approximately 21M, and-XX:MaxMetaspaceSizedefaults to -1 (unlimited). - Unlike PermGen, if size is not specified, the virtual machine consumes all available system memory. If metaspace overflow occurs, the VM throws OutOfMemoryError: Metaspace.
-XX:MetaspaceSizesets the initial metaspace size. For a 64-bit server JVM, the default-XX:MetaspaceSizeis 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.- 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:MetaspaceSizeto 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
- 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.
- Memory Leak occurs when numerous references point to objects that will no longer be used but remain reachable from GC Roots, preventing their collection.
- 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.
- If no memory leak exists (objects in memory are genuinely needed), examine JVM heap parameters (
-Xmxand-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:
- The type's fully qualified name (package.name format)
- The type's direct parent class's fully qualified name (interfaces and java.lang.Object have no parent)
- The type's modifiers (public, abstract, final subset)
- An ordered list of the type's direct interfaces
Field Information
Field information corresponds to member variables:
- The JVM must store all field-related information and declaration order.
- 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:
- Method name
- Method return type (including void), where void maps to void.class in Java
- Method parameter count and types (in order)
- Method modifiers (public, private, protected, static, final, synchronized, native, abstract subset)
- Method bytecode, operand stack size, local variable table size (except for abstract and native methods)
- 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
- descriptor: I indicates the field type is Integer
- flags: ACC_PUBLIC indicates the field modifier is public
Method Information
- descriptor: ()V indicates the method return type is void
- flags: ACC_PUBLIC indicates the method modifier is public
- stack=3 indicates the operand stack depth is 3
- locals=2 indicates there are 2 local variables (instance methods include this)
- The test1() method has no parameters, but its args_size=1 because this is passed as a parameter
Non-Final Class Variables
- Static variables are associated with classes and load with them, becoming part of class data logically
- 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
- Global constants are declared with static final
- 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
- The method area internally contains the runtime constant pool
- Bytecode files internally contain the constant pool (visible in the bytecode files shown earlier)
- Understanding the method area requires understanding ClassFile structure, as loaded class information resides there
- Understanding the runtime constant pool requires understanding the constant pool table within ClassFile
Constant Pool
- 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
- Literals: values like 10, "hello" are literals
Why is a Constant Pool Needed?
- 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
- Numeric values
- String values
- Class references
- Field references
- 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
- The runtime constant pool is part of the method area
- 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)
- Upon loading classes and interfaces into the VM, the corresponding runtime constant pool is created
- The JVM maintains a constant pool for each loaded type (class or interface). Pool data is accessed via indices like array elements
- 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.
- The runtime constant pool resembles the symbol table in traditional programming languages but contains richer data
- 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
- Initial state
- Push operand 500 onto the operand stack
- 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
- References to System class (corrected: #25 and #26) 12-13. Continue processing
- After addition operation, store result at operand stack top
- Actual print operation
- 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
- 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.
- 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
- With Java 8, HotSpot VM no longer has PermGen. However, class metadata was not eliminated—moved to native memory outside the heap called Metaspace.
- Since class metadata is allocated in native memory, metaspace's maximum size is the system's available memory.
- 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.
- 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:
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:
- Object instances referenced by static references (new byte[1024 * 1024 * 100]) always exist in heap space
- 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
- staticObj is stored with Test's type information in the method area
- instanceObj is stored with Test's object instance in the Java heap
- localObj is stored in foo()'s stack frame local variable table
- 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
- 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.
- 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.
- Method area GC primarily collects two things: abandoned constants and unused types.
- 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
- HotSpot's constant pool collection strategy is clear: constants with no references from anywhere can be collected.
- 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
- new: Most common, single instance calls to getInstance() static method, XXXFactory static method
- Class.newInstance: Deprecated in JDK9; only invokes no-arg constructor with public access
- Constructor.newInstance: Reflection approach, invokes no-arg or parameterized constructors
- clone(): Does not invoke any constructor; requires implementing Cloneable interface's clone method
- Deserialization: Binary stream from file or network; commonly used for socket network transmission
- Third-party library Objenesis
Object Creation Steps
Checking if the Class is Loaded
- 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
- 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
- Calculate object size first, then allocate memory in the heap. If instance member variables are reference types, only allocate reference variable space (4 bytes)
- 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
- 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
- CAS with retry on failure ensures atomicity
- Pre-allocate TLAB per thread via
-XX:+UseTLAB(area locking mechanism) - 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:
- Default value initialization
- Explicit initialization/code block initialization (parallel, order determined by code)
- 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
- 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
- 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
- Disadvantage: Allocates space in heap for handle pool; handles consume memory; two pointer traversals required to access heap objects, lower efficiency
- 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)
- Advantage: Reference in local variable table points directly to heap instance; instance contains type pointer pointing to method area type data
- Disadvantage: When objects move (common during GC), reference values must be updated
Direct Memory
Direct Memory Overview
- Not part of the JVM runtime data area or memory region defined by the Java Virtual Machine Specification
- Native memory outside the Java heap, directly requested from the system
- Originates from NIO, operating native memory through heap-based DirectByteBuffer
- Direct memory access is typically faster than Java heap—higher read/write performance
- For performance-critical, frequently read/write scenarios, consider using direct memory
- 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
- Direct memory can also cause OutOfMemoryError
- Direct memory is outside the Java heap, so its size is not directly limited by
-Xmxmaximum heap size. However, system memory is finite—the total of Java heap and direct memory is still limited by OS-available memory - Direct memory disadvantages:
- Higher allocation/recycling cost
- Not managed by JVM garbage collection
- Direct memory size can be configured via MaxDirectMemorySize
- If unspecified, defaults match the
-Xmxheap 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
- Baidu:
- Third round: How is an object stored in the JVM? What does the object header contain?
- Ant Group:
- Second round: What is inside the Java object header?
- Xiaomi:
- JVM memory partitioning; why have young and old generations?
- ByteDance:
- Second round: Java memory areas
- Second round: VM runtime data area
- When do objects enter the old generation?
- 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?
- Tmall:
- First round: JVM memory model and areas, details for each area's purpose
- First round: JVM memory model changes in Java 8
- Pinduoduo:
- JVM memory partitioning and each area's function?
- Meituan:
- Java memory allocation
- Does garbage collection occur in JVM PermGen?
- First round: JVM memory partitioning, why have young and old generations?