The JVM Memory Model
The JVM architecture primarily consists of the class loader system, memory management, bytecode execution engine, and garbage collection mechanisms.
Class Loading Mechanism
The class loader hierarchy includes the Bootstrap ClassLoader, Extension ClassLoader, and Application ClassLoader.
The Bootstrap ClassLoader handles core Java classes, typically found in rt.jar.
The Extension ClassLoader manages classes located in the java.ext directory.
The Application ClassLoader loads user-defined classes.
The loading strategy employs both the parenet delegation model and the full-delegation principle.
In the parent delegation model, when loading a class, the current loader first checks its parent for availability. If the parent cannot load it, the request is passed down until the Bootstrap ClassLoader attempts to load core classes. Since it can't load application classes, the responsibility returns up the chain until the Application ClassLoader loads them.
The full-delegation principle means that if no explicit delegation occurs, all classes within a loader's scope are processed.
To override the parent delegation mechanism, one can examine the source code:
1 protected Class<?> loadClass(String name, boolean resolve)
2 throws ClassNotFoundException
3 {
4 synchronized (getClassLoadingLock(name)) {
5 // First, check if the class has already been loaded
6 Class<?> c = findLoadedClass(name);
7 if (c == null) {
8 long t0 = System.nanoTime();
9 try {
10 if (parent != null) {
11 c = parent.loadClass(name, false);
12 } else {
13 c = findBootstrapClassOrNull(name);
14 }
15 } catch (ClassNotFoundException e) {
16 // ClassNotFoundException thrown if class not found
17 // from the non-null parent class loader
18 }
19
20 if (c == null) {
21 // If still not found, then invoke findClass in order
22 // to find the class.
23 long t1 = System.nanoTime();
24 c = findClass(name);
25 // this is the defining class loader; record the stats
26 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
27 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
28 sun.misc.PerfCounter.getFindClasses().increment();
29 }
30 }
31 if (resolve) {
32 resolveClass(c);
33 }
34 return c;
35 }
36 }
The parent.loadClass call indicates where to modify the behavior by overriding loadClass and findClass.
JVM Memory Areas
Let's analyze the operand stack and local variable table with a simple example:
1 //
2 // Source code recreated from a .class file by IntelliJ IDEA
3 // (powered by Fernflower decompiler)
4 //
5
6 package com.tuling;
7
8 public class Main {
9 public Main() {
10 }
11
12 public int add() {
13 int a = 1;
14 int b = 2;
15 int c = (a + b) * 10;
16 return c;
17 }
18
19 public static void main(String[] args) {
20 Main main = new Main();
21 main.add();
22 System.out.println("aaa");
23 }
24 }
By disassembling main.class with javap -c, we get the following bytecode:
1 Classfile /E:/йљПЁгъ/01java-vip/tuling-vip-spring/springannopriciple01/target/test-classes/com/tuling/Main.class
2 Last modified 2019-9-8; size 714 bytes
3 MD5 checksum 316510b260c590e9dd45038da671e84e
4 Compiled from "Main.java"
5 public class com.tuling.Main
6 minor version: 0
7 major version: 52
8 flags: ACC_PUBLIC, ACC_SUPER
9 Constant pool:
10 #1 = Methodref #8.#28 // java/lang/Object."<init>":()V
11 #2 = Class #29 // com/tuling/Main
12 #3 = Methodref #2.#28 // com/tuling/Main."<init>":()V
13 #4 = Methodref #2.#30 // com/tuling/Main.add:()I
14 #5 = Fieldref #31.#32 // java/lang/System.out:Ljava/io/PrintStream;
15 #6 = String #33 // aaa
16 #7 = Methodref #34.#35 // java/io/PrintStream.println:(Ljava/lang/String;)V
17 #8 = Class #36 // java/lang/Object
18 #9 = Utf8 <init>
19 #10 = Utf8 ()V
20 #11 = Utf8 Code
21 #12 = Utf8 LineNumberTable
22 #13 = Utf8 LocalVariableTable
23 #14 = Utf8 this
24 #15 = Utf8 Lcom/tuling/Main;
25 #16 = Utf8 add
26 #17 = Utf8 ()I
27 #18 = Utf8 a
28 #19 = Utf8 I
29 #20 = Utf8 b
30 #21 = Utf8 c
31 #22 = Utf8 main
32 #23 = Utf8 ([Ljava/lang/String;)V
33 #24 = Utf8 args
34 #25 = Utf8 [Ljava/lang/String;
35 #26 = Utf8 SourceFile
36 #27 = Utf8 Main.java
37 #28 = NameAndType #9:#10 // "<init>":()V
38 #29 = Utf8 com/tuling/Main
39 #30 = NameAndType #16:#17 // add:()I
40 #31 = Class #37 // java/lang/System
41 #32 = NameAndType #38:#39 // out:Ljava/io/PrintStream;
42 #33 = Utf8 aaa
43 #34 = Class #40 // java/io/PrintStream
44 #35 = NameAndType #41:#42 // println:(Ljava/lang/String;)V
45 #36 = Utf8 java/lang/Object
46 #37 = Utf8 java/lang/System
47 #38 = Utf8 out
48 #39 = Utf8 Ljava/io/PrintStream;
49 #40 = Utf8 java/io/PrintStream
50 #41 = Utf8 println
51 #42 = Utf8 (Ljava/lang/String;)V
52 {
53 public com.tuling.Main();
54 descriptor: ()V
55 flags: ACC_PUBLIC
56 Code:
57 stack=1, locals=1, args_size=1
58 0: aload_0
59 1: invokespecial #1 // Method java/lang/Object."<init>":()V
60 4: return
61 LineNumberTable:
62 line 10: 0
63 LocalVariableTable:
64 Start Length Slot Name Signature
65 0 5 0 this Lcom/tuling/Main;
66
67 public int add();
68 descriptor: ()I
69 flags: ACC_PUBLIC
70 Code:
71 stack=2, locals=4, args_size=1
72 0: iconst_1
73 1: istore_1
74 2: iconst_2
75 3: istore_2
76 4: iload_1
77 5: iload_2
78 6: iadd
79 7: bipush 10
80 9: imul
81 10: istore_3
82 11: iload_3
83 12: ireturn
84 LineNumberTable:
85 line 13: 0
86 line 14: 2
87 line 15: 4
88 line 16: 11
89 LocalVariableTable:
90 Start Length Slot Name Signature
91 0 13 0 this Lcom/tuling/Main;
92 2 11 1 a I
93 4 9 2 b I
94 11 2 3 c I
95
96 public static void main(java.lang.String[]);
97 descriptor: ([Ljava/lang/String;)V
98 flags: ACC_PUBLIC, ACC_STATIC
99 Code:
100 stack=2, locals=2, args_size=1
101 0: new #2 // class com/tuling/Main
102 3: dup
103 4: invokespecial #3 // Method "<init>":()V
104 7: astore_1
105 8: aload_1
106 9: invokevirtual #4 // Method add:()I
107 12: pop
108 13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
109 16: ldc #6 // String aaa
110 18: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
111 21: return
112 LineNumberTable:
113 line 20: 0
114 line 21: 8
115 line 22: 13
116 line 23: 21
117 LocalVariableTable:
118 Start Length Slot Name Signature
119 0 22 0 args [Ljava/lang/String;
120 8 14 1 main Lcom/tuling/Main;
121 }
122 SourceFile: "Main.java"
Focusing on the add method, we see bytecode instructions like:
iconst_1: Pushes integer value 1 onto the stack.
istore_1: Stores the top stack value into local variable slot 1.
This stack refers to the operand stack.
Garbage Collection Algorithms: Mark-Sweep, Mark-Compact, Copying, Generational
Mark-Sweep: Involves marking objects to be collected followed by sweeping and deleting marked objects. It's the fundamental approach.
Mark-Compact: Similar to Mark-Sweep but moves live objects toward one end before cleaning the rest.
Copying: Divides memory into two equal halves. When one half fills, live objects are copied to the other half, clearing the first.
Why Garbage Collection?
Examining heap structure reveals memory usage patterns.
To inspect your application's heap, use jps to identify the process ID, then run jmap -heap [PID] to view heap details, such as:
1 Attaching to process ID 7964, please wait...
2 Debugger attached successfully.
3 Server compiler detected.
4 JVM version is 25.73-b02
5
6 using thread-local object allocation.
7 Parallel GC with 4 thread(s)
8
9 Heap Configuration:
10 MinHeapFreeRatio = 0
11 MaxHeapFreeRatio = 100
12 MaxHeapSize = 2128609280 (2030.0MB)
13 NewSize = 44564480 (42.5MB)
14 MaxNewSize = 709361664 (676.5MB)
15 OldSize = 89653248 (85.5MB)
16 NewRatio = 2
17 SurvivorRatio = 8
18 MetaspaceSize = 21807104 (20.796875MB)
19 CompressedClassSpaceSize = 1073741824 (1024.0MB)
20 MaxMetaspaceSize = 17592186044415 MB
21 G1HeapRegionSize = 0 (0.0MB)
22
23 Heap Usage:
24 PS Young Generation
25 Eden Space:
26 capacity = 34078720 (32.5MB)
27 used = 4771760 (4.5507049560546875MB)
28 free = 29306960 (27.949295043945312MB)
29 14.002169095552885% used
30 From Space:
31 capacity = 5242880 (5.0MB)
32 used = 0 (0.0MB)
33 free = 5242880 (5.0MB)
34 0.0% used
35 To Space:
36 capacity = 5242880 (5.0MB)
37 used = 0 (0.0MB)
38 free = 5242880 (5.0MB)
39 0.0% used
40 PS Old Generation
41 capacity = 89653248 (85.5MB)
42 used = 0 (0.0MB)
43 free = 89653248 (85.5MB)
44 0.0% used
If the old generation isn't full, a Full GC won't occur. Young GC happens upon filling the young generation, with out stopping the world.
Available Garbage Collectors
Types include Serial, ParNew, Parallel Scavenge, Serial Old, Parallel Old, CMS, and G1 collectors.
Serial Collector: Uses copying in the young generation and mark-compact in the old generation. Operates single-threaded, pausing all threads during collection.
ParNew Collector: Copies in the young generation and marks-compact in the old. Utilizes multiple threads for GC but pauses application threads.
Parallel Scavenge Collector: Similar to ParNew but focuses on throughput.
Serial Old and Parallel Old Collectors: Serve as older-generation versions of Serial and Parallel Scavenge collectors respectively, often combined with other collectors.
CMS Collector: Reduces stop-the-world time by marking reachable nodes concurrently, then performs final marking and cleanup with minimal pause.
G1 Collector: Divides heap into regions. Unlike tradisional generations, it maintains logical separation. Large objects go directly into Humongous regions to avoid frequent Full GCs. It allows users to specify collection time goals, prioritizing efficient collection based on cost-benefit analysis.