文章目录

Java 内存模型:JVM 内存结构与 GC 算法

发布于 2026-04-08 17:28:19 · 浏览 4 次 · 评论 0 条

Java内存模型:JVM内存结构与GC算法

一、Java内存模型概述

理解 Java内存模型(Java Memory Model, JMM)是Java虚拟机规范中定义的一组规则,它规定了线程如何以及何时可以看到其他线程共享变量的修改,以及如何同步访问共享变量。

区分 Java内存模型与JVM内存结构是两个不同的概念。JMM是关于多线程内存访问的抽象模型,而JVM内存结构是Java程序运行时内存的物理划分。

明确 JMM的主要目标是定义程序中各种变量的访问规则,以及在并发环境下如何保证共享变量的正确可见性和有序性。


二、JVM内存结构详解

认识 JVM运行时内存结构主要分为以下几个区域:

1. 程序计数器(PC Register)

定义 程序计数器是一块较小的内存空间,它是一个当前线程所执行的字节码行号指示器。

功能

  • 存储下一条要执行的JVM指令地址
  • 线程私有,不会出现内存溢出

2. 虚拟机栈(JVM Stack)

定义 虚拟机栈是Java方法执行的内存模型,每个方法执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

特点

  • 线程私有
  • 栈深度由虚拟机栈大小决定
  • 可能出现StackOverflowError(线程请求栈深度超过虚拟机栈最大深度)和OutOfMemoryError(虚拟机栈可扩展时无法申请足够内存)

包含 每个栈帧包含:

  • 局部变量表
  • 操作数栈
  • 动态链接
  • 方法返回地址
  • 额外信息

3. 本地方法栈(Native Method Stack)

定义 本地方法栈与虚拟机栈类似,区别是它为虚拟机使用到的Native方法服务。

特点

  • 线程私有
  • 可能出现StackOverflowErrorOutOfMemoryError

4. 堆(Heap)

定义 Java堆是Java虚拟机所管理的内存中最大的一块,它被所有线程共享,在虚拟机启动时创建。

特点

  • 所有线程共享
  • 唯一目的存放对象实例和数组
  • 是GC管理的主要区域
  • 可能出现OutOfMemoryError

细分 堆内部可以细分为:

  • 新生代(Eden区、From Survivor区、To Survivor区)
  • 老年代

5. 方法区(Method Area)

定义 方法区用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

特点

  • 各线程共享
  • 不进行垃圾收集
  • 可能出现OutOfMemoryError

替代 在JDK 8及以后版本,方法区被元空间(Metaspace)替代,使用本地内存而非JVM内存。

6. 运行时常量池(Runtime Constant Pool)

定义 运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。

特点

  • 各线程共享
  • 可能出现OutOfMemoryError

7. 直接内存(Direct Memory)

定义 直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。

特点

  • 通过NIO使用Native函数库直接分配堆外内存
  • 可能出现OutOfMemoryError
  • 分配和回收成本较高,但读写性能高

三、垃圾回收基础

1. 为什么需要垃圾回收

明确 在C/C++中,对象内存需要手动分配和释放,容易导致内存泄漏和野指针问题。

理解 Java垃圾回收器(GC)自动管理内存,回收不再使用的对象,避免内存泄漏。

2. 垃圾回收的意义

防止 内存溢出
提高 开发效率
增强 程序稳定性

3. 垃圾回收的判断方式

引用计数法

原理 给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1;引用失效时,计数器减1;计数器为0时对象可回收。

缺点 无法解决对象循环引用问题

可达性分析算法

原理 通过一系列称为"GC Roots"的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。

判定 当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

GC Roots包括

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象

4. 垃圾回收的四种引用级别

强引用(Strong Reference)

  • 特点:只要强引用存在,垃圾回收器永远不会回收
  • 场景:最常见的引用形式

软引用(Soft Reference)

  • 特点:内存不足时会被回收
  • 场景:内存敏感缓存

弱引用(Weak Reference)

  • 特点:生存期短于强引用,只能存活到下一次垃圾回收前
  • 场景:非必须对象

虚引用(Phantom Reference)

  • 特点:不会决定对象的生命周期,主要用于跟踪垃圾回收过程
  • 场景:跟踪对象被回收状态

四、GC算法详解

1. 标记-清除算法(Mark-Sweep)

原理

  1. 标记:遍历所有对象,标记所有需要回收的对象
  2. 清除:遍历整个内存区域,回收被标记的对象

优点

  • 简单易实现
  • 不需要移动对象

缺点

  • 效率不高
  • 产生内存碎片

2. 标记-整理算法(Mark-Compact)

原理

  1. 标记:遍历所有对象,标记所有需要回收的对象
  2. 整理:将存活对象向内存一端移动,直接清理端边界以外的内存

优点

  • 没有内存碎片
  • 适合老年代回收

缺点

  • 移动对象开销大
  • 效率较低

3. 复制算法(Copying)

原理 将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这块内存用完了,就将还存活的对象复制到另一块,再把已使用的内存空间一次清理掉。

优点

  • 没有内存碎片
  • 回收效率高

缺点

  • 内存空间减半
  • 存活对象多时,复制效率低

4. 分代收集算法

原理 根据对象存活周期的不同,将内存划分为几块,一般是把Java堆分为新生代和老年代。

新生代特点

  • 对象存活时间短
  • 适合复制算法

老年代特点

  • 对象存活时间长
  • 适合标记-清除或标记-整理算法

新生代回收过程

  1. 分配 新对象优先在Eden区分配
  2. 第一次GC 当Eden区满时,触发Minor GC,将存活对象复制到Survivor区
  3. 后续GC 每次Minor GC都会在两个Survivor区间移动对象,并增加对象的年龄计数器
  4. 晋升 当对象年龄达到阈值(默认15)或Survivor空间不足时,对象晋升到老年代

老年代回收

当老年代空间不足时,触发Major GC(或Full GC),通常采用标记-清除或标记-整理算法。

5. 增量收集算法与分区收集算法

增量收集算法

  • 原理:将回收操作分批进行,减少应用停顿时间
  • 目的:减少系统停顿

分区收集算法

  • 原理:将整个堆划分为连续的不同小区间,每个区间独立回收
  • 目的:控制回收时间,获得更短的停顿时间

五、GC实现与调优

1. HotSpot垃圾收集器

Serial收集器

特点

  • 单线程工作
  • 进行GC时,必须暂停所有用户线程
  • 简单高效
  • 适合客户端模式

ParNew收集器

特点

  • Serial收集器的多线程版本
  • 多线程回收
  • 需要暂停用户线程
  • 在服务端模式下首选新生代收集器

Parallel Scavenge收集器

特点

  • 高吞吐量优先的收集器
  • 多线程复制算法
  • 可控制吞吐量
  • 适合后台计算

Serial Old收集器

特点

  • Serial收集器的老年代版本
  • 单线程标记-整理算法
  • 主要用于Client模式

Parallel Old收集器

特点

  • Parallel Scavenge的老年代版本
  • 多线程标记-整理算法
  • 注重吞吐量

CMS(Concurrent Mark Sweep)收集器

特点

  • 以获取最短回收停顿时间为目标
  • 基于标记-清除算法
  • 并发标记与并发清除
  • 四个阶段:初始标记、并发标记、重新标记、并发清除

缺点

  • 产生内存碎片
  • CPU资源敏感
  • 无法处理浮动垃圾

G1(Garbage-First)收集器

特点

  • 面向服务端应用
  • 并行与并发
  • 空间整合
  • 可预测停顿时间

设计理念

  • 将整个Java堆划分为多个大小相等的独立区域(Region)
  • 跟踪每个Region里垃圾的价值(回收空间大小与回收时间)
  • 在有限时间内,回收价值最大的Region

2. GC调优

监控GC

  • 使用jstat命令监控GC情况
  • 使用jvisualvm可视化工具

分析GC日志

  • 启用GC日志:-Xloggc:gc.log -XX:+PrintGCDetails
  • 分析GC频率、停顿时间、吞吐量

调优原则

  1. 大多数应用不需要调优
  2. 明确调优目标:吞吐量还是响应时间
  3. 理解应用特性

常用参数调优

  • 堆大小设置:-Xms(初始堆大小) -Xmx(最大堆大小)
  • 新生代大小:-Xmn-XX:SurvivorRatio-XX:NewRatio
  • GC类型选择:-XX:+UseSerialGC-XX:+UseParallelGC-XX:+UseConcMarkSweepGC-XX:+UseG1GC
  • GC日志配置:-Xloggc:文件名 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

调优步骤

  1. 确定调优目标
  2. 基线测试
  3. 分析GC日志
  4. 确定瓶颈
  5. 调整参数
  6. 验证效果

六、总结

掌握 JVM内存结构和GC机制是Java开发者必备的基础知识,有助于编写更高效的程序并解决内存相关的问题。

理解 不同GC算法适用于不同的应用场景,根据应用需求选择合适的垃圾收集器至关重要。

实践 通过监控和分析GC日志,可以了解程序内存使用情况,并进行针对性调优,提高系统性能。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文