当前位置: 首页 > news >正文

以计数器程序为例,简析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());}
}

一、类加载阶段(方法区)

  1. 加载

    • JVM通过类加载器查找Counter.class字节码文件
    • 对于主类Counter,通常由应用类加载器(AppClassLoader)加载
  2. 验证

    • 检查字节码是否符合规范(比如魔数0xCAFEBABE)
    • 验证count++操作是否合法
  3. 准备

    • 为类变量分配内存并初始化默认值
    • count被初始化为0(不是程序中的显式赋值)
  4. 解析

    • 将符号引用(如System.out)转换为直接引用
  5. 初始化

    • 执行类构造器<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()

  • 局部变量表:存储argscounter引用
  • 操作数栈
    操作数栈步骤:
    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()字节码

通过这个例子可以看到:

  1. 类信息存储在方法区
  2. 对象实例在堆中分配
  3. 方法调用通过栈帧实现
  4. 线程通过工作内存与主内存交互
  5. GC自动管理堆内存生命周期

这就是JVM运行Java程序的完整过程,各内存区域协同工作,共同支持程序的执行。

http://www.lqws.cn/news/498817.html

相关文章:

  • 72-Oralce Temporay tablespace(单实例和多租户下的管理)
  • 互联网大数据求职面试:从Zookeeper到Flink的技术探讨
  • 华为云Flexus+DeepSeek征文|基于Dify构建抓取金融新闻并发送邮箱工作流
  • 实现 “WebView2 获取word选中内容
  • 板凳-------Mysql cookbook学习 (十--9)
  • TCP客户端发送消息失败(NetAssist做客户端)
  • Java底层原理:深入理解JVM内存管理机制
  • 在Springboot项目部署时遇到,centos服务器上,curl请求目标地址不通 ,curl -x 可以请求通的解决办法
  • AWS服务器扩充硬盘
  • 汽车制造领域:EtherCAT转Profinet网关案例全面解析
  • Threejs实现 3D 看房效果
  • 基于ASP4644多通道降压技术在电力监测系统中集成应用与发展前景
  • 使用Windows自带的WSL安装Ubuntu Linux系统
  • Python 数据分析与可视化 Day 5 - 数据可视化入门(Matplotlib Seaborn)
  • 《Redis高并发优化策略与规范清单:从开发到运维的全流程指南》
  • 打包winform
  • 使用uv安装python任意版本,命令:uv python install
  • 数组题解——​最大子数组和​【LeetCode】(更新版)
  • (nice!!!)(LeetCode 每日一题) 2081. k 镜像数字的和 (枚举)
  • (cvpr2025) DefMamba: Deformable Visual State Space Model
  • 008 Linux 开发工具(下) —— make、Makefile、git和gdb
  • VitePress搭建静态博客
  • logstash读取kafka日志写到oss归档存储180天
  • 提示词模板设计:LangGPT的提示词设计框架
  • RK3288 android7.1 将普通串口设置为调试串口
  • WinUI3入门8:解决release版异常 取消优化和裁剪
  • QML革命:下一代GUI开发的核心优势详解
  • WebSocket 端点 vs Spring Bean
  • PyTorch 实现的 GlobalPMFSBlock_AP_Separate:嵌套注意力机制在多尺度特征聚合中的应用
  • LLM 编码器 怎么实现语义相关的 Token 向量更贴近? mask训练:上下文存在 ;; 自回归训练:只有上文,生成模型