以计数器程序为例,简析JVM内存模型中各部分的工作方式
以计数器程序为例,简析JVM内存模型中各部分的工作方式
示例程序:简单的计数器程序
public class Counter {private int count = 0;public void increment() {count++;}public int getCount() {return count;}public static void main(String[] args) {Counter counter = new Counter();counter.increment();System.out.println("Count: " + counter.getCount());}
}
一、类加载阶段(方法区)
-
加载:
- JVM通过类加载器查找
Counter.class
字节码文件 - 对于主类
Counter
,通常由应用类加载器(AppClassLoader)加载
- JVM通过类加载器查找
-
验证:
- 检查字节码是否符合规范(比如魔数0xCAFEBABE)
- 验证
count++
操作是否合法
-
准备:
- 为类变量分配内存并初始化默认值
count
被初始化为0(不是程序中的显式赋值)
-
解析:
- 将符号引用(如
System.out
)转换为直接引用
- 将符号引用(如
-
初始化:
- 执行类构造器
<clinit>()
(本例中没有静态变量/块,不生成)
- 执行类构造器
二、内存分配(堆 & 栈)
1. 主线程启动(线程私有区域)
- 创建主线程的程序计数器(PC)和Java虚拟机栈
- PC初始指向
main()
方法的第一条指令
2. 执行main方法(栈帧详解)
public static void main(String[] args) {Counter counter = new Counter(); // 栈帧1counter.increment(); // 栈帧2System.out.println(...); // 栈帧3
}
栈帧1:new Counter()
- 局部变量表:存储
args
和counter
引用 - 操作数栈:
操作数栈步骤: 1. new #2 // 在堆中分配内存 2. dup // 复制引用 3. invokespecial #3 // 调用构造方法<init> 4. astore_1 // 存储引用到局部变量counter
- 堆内存变化:
- 在Eden区创建Counter对象实例
- 对象头包含类元数据指针(指向方法区)
- 实例数据区存储
count=0
(准备阶段后的显式初始化)
栈帧2:increment()
调用
- 动态链接:指向方法区的
increment()
方法字节码 - 操作数栈执行
count++
:1. aload_0 // 加载this引用 2. dup // 复制引用 3. getfield #4 // 读取count值到栈顶 (0) 4. iconst_1 // 压入常量1 5. iadd // 栈顶两数相加 (0+1=1) 6. putfield #4 // 写回count字段
- 堆内存变化:Counter对象的count字段更新为1
栈帧3:println()
调用
- 涉及字符串拼接(触发StringBuilder操作)
- 在堆中创建"Count: 1"字符串对象
三、内存模型关键点示例
1. 对象内存布局(64位JVM示例)
Counter对象实例(堆中):
+-------------------+
| 对象头 (12字节) | # MarkWord + 类指针
+-------------------+
| count (4字节) | # 实际值为1
+-------------------+
| 对齐填充 (可能0字节)|
+-------------------+
2. 方法区存储示例
Counter类信息(方法区):
- 类全名:org/example/Counter
- 字段信息:count (I)
- 方法字节码:- increment():aload_0, dup, getfield, ...- getCount():aload_0, getfield, ireturn
- 运行时常量池:#4 = Fieldref #2.#3 // Counter.count:I#5 = String #6 // "Count: "
3. 多线程场景演示
如果多个线程调用counter.increment()
:
// 线程1工作内存:count=1 → 读取→计算→写入2
// 线程2工作内存:count=1 → 读取→计算→写入2
问题:非原子操作导致结果可能为2而不是3
解决方案:
- 加
synchronized
(在栈帧中通过monitorenter/monitorexit实现) - 使用
AtomicInteger
(基于CAS的CPU原子指令)
四、垃圾回收示例
1. 内存状态执行后
堆内存:
Eden区:["Count: 1"字符串对象]
Survivor区:[Counter对象](年龄=1)
老年代:(空)
2. GC过程模拟
- Minor GC:
- 扫描Eden区,字符串对象无引用→回收
- Counter对象被移到Survivor区(年龄+1)
- Full GC(假设多次调用后):
- Counter对象年龄达15→晋升老年代
五、可视化执行流程
[方法区] Counter类信息↑
[堆] Counter实例 ←→ [主线程栈]局部变量表:[counter=0x3E4]操作数栈:[执行iadd指令]动态链接:指向increment()字节码
通过这个例子可以看到:
- 类信息存储在方法区
- 对象实例在堆中分配
- 方法调用通过栈帧实现
- 线程通过工作内存与主内存交互
- GC自动管理堆内存生命周期
这就是JVM运行Java程序的完整过程,各内存区域协同工作,共同支持程序的执行。