网站首页 > 博客文章 正文
共同学习,有错欢迎指出。
JVM 运行时数据区域
1. 程序计数器
程序计数器是一块较小的内存空间,可看作当前线程所执行的字节码的行号指示器。在虚拟机概念模型里,字节码解释器通过改变这个计数器的值选取下一条字节码指令,分支、循环、跳转等基础功能均依赖此计数器完成,是线程私有的内存。
2. Java 虚拟机栈
与程序计数器一样,Java 虚拟机栈也是线程私有的,生命周期与线程相同。它描述 Java 方法执行的内存模型:每个方法执行时创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法从调用到执行完成,对应栈帧在虚拟机栈中入栈到出栈的过程。
3. 本地方法栈
本地方法栈与虚拟机栈作用相似,区别在于虚拟机栈为 Java 方法(字节码)服务,而本地方法栈为虚拟机使用的 Native 方法服务。
4. Java 堆
对于大多数应用,Java 堆是 JVM 管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建。此区域唯一目的是存放对象实例,几乎所有对象实例都在此分配内存。
5. 方法区
方法区用于存储已加载的类信息、常量、静态变量(如 static 修饰的变量)。运行时常量池是方法区的一部分,class 文件中的常量池存放编译期生成的字面量和符号引用。
老版 JDK 中,方法区称为永久代。
JDK 1.8 后废弃永久代,引入元空间(Metaspace),元空间是 HotSpot JVM 对方法区的实现,使用本地内存,理论上受限于系统虚拟内存大小(需配置参数)。
分代回收
年轻代结构
HotSpot JVM 将年轻代分为 1 个 Eden 区和 2 个 Survivor 区(From 和 To)。新创建的对象通常分配到 Eden 区(大对象特殊处理),首次 Minor GC 后存活的对象移至 Survivor 区。对象在 Survivor 区每熬过一次 Minor GC,年龄增加 1 岁,达到阈值后移至老年代。
复制算法原理
年轻代采用复制算法,将内存分为两块,每次只用一块,满后将存活对象复制到另一块,不产生内存碎片。
GC 流程:
GC 开始时,对象存在于 Eden 和 From 区,To 区为空。
Eden 和 From 区存活对象复制到 To 区,From 区中年龄达阈值的对象移至老年代,未达阈值的复制到 To 区。
GC 后,Eden 和 From 区清空,From 与 To 角色互换,确保 To 区始终为空。
当 To 区填满时,所有对象移至老年代。
动态年龄计算
HotSpot 遍历对象时,按年龄从小到大累积占用大小,当某年龄对象总和超过 Survivor 区一半时,取该年龄与-XX:MaxTenuringThreshold的较小值作为新晋升阈值。设计目的:
避免固定阈值导致 “过早晋升” 或 “Survivor 溢出”,动态适应对象生命周期波动。
常见垃圾回收机制
1. 引用计数法
每个对象含引用计数器,引用增加则 + 1,失效则 - 1。计数器为 0 时释放内存。优点是简单,缺点是无法处理循环引用,且计数开销持续存在。
2. 可达性分析算法
以GC Roots(如虚拟机栈引用、方法区静态变量、常量引用、本地方法栈 JNI 引用等)为起点,通过引用链判断对象是否可达。不可达的对象判定为可回收。
CMS 收集器执行过程
初始标记(STW initial mark):从 GC Roots 开始,仅扫描直接关联对象并标记,停顿时间短。
并发标记(Concurrent marking):在初始标记基础上继续追溯标记,与用户线程并发执行。
并发预清理(Concurrent precleaning):扫描并发标记阶段新进入老年代的对象,减少重新标记工作量。
重新标记(STW remark):暂停虚拟机,扫描 CMS 堆剩余对象,处理引用变更。
并发清理(Concurrent sweeping):清理垃圾对象,与用户线程并发执行。
并发重置(Concurrent reset):重置 CMS 数据结构,等待下次 GC。
G1 收集器执行过程
标记阶段:
初始标记(Initial-Mark,STW):触发一次 Young GC,标记 GC Roots 直接关联对象。
并发标记:全堆扫描,与用户线程并发执行,计算区域对象活性,回收全垃圾区域。
再标记(STW):使用 SATB 算法补充标记并发阶段新增垃圾。
清理阶段(STW):选择活性低的区域,等待下次 Young GC 回收。
回收 / 完成:Young GC 清理选定区域,可能保留部分高活性区域。
G1 与 CMS 对比
特性 | CMS | G1 |
目标 | 最短回收停顿时间 | 高吞吐量 + 短停顿,适合大内存 |
作用区域 | 老年代 | 全堆(分 Region) |
算法 | 标记 - 清除,易产生碎片 | 局部复制 + 整体标记 - 整理,碎片少 |
浮动垃圾处理 | 无法处理,需下次 GC | 可处理 |
内存占用 | 小(Card Table) | 大(RSet,约占堆内存 20%+) |
默认启用版本 | JDK 1.5-1.8 | JDK 9+ |
适用场景 | 小内存、低延迟场景 | 大内存(6GB+)、高并发场景 |
GC Roots 对象
虚拟机栈(栈帧本地变量表)引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中 JNI 引用的对象。
Stop the World(STW)
GC 执行时,除 GC 线程外的所有用户线程挂起,直至 GC 完成。STW 是保证 GC 正确性的必要机制,但会影响实时性。可通过选择并发收集器(如 CMS、G1)降低停顿时间。
垃圾回收算法
停止 - 复制(Copying):暂停程序,将存活对象从当前堆复制到另一堆,适合新生代短生命周期对象,缺点是空间利用率低。
标记 - 清除(Mark-Sweep):标记存活对象,清除未标记对象,产生内存碎片,适合老年代。
标记 - 整理(Mark-Compact):标记后整理存活对象,压缩内存,避免碎片,适合老年代。
分代收集算法:新生代用复制算法,老年代用标记 - 清除或标记 - 整理算法,结合各代特点优化回收效率。
Minor GC 与 Full GC 触发条件
Minor GC 触发:Eden 区满时触发。
Full GC 触发:
调用System.gc()(建议而非强制)。
老年代空间不足。
方法区空间不足(JDK 8 前永久代)。
Minor GC 后进入老年代的对象大小超过老年代可用内存。
新生代复制对象时,目标 Survivor 区空间不足,需晋升老年代但老年代空间不足。
对象进入老年代的条件
大对象直接进入:超过
-XX:PretenureSizeThreshold(默认 3M)的对象直接在老年代分配。
长期存活对象:年龄超过-XX:MaxTenuringThreshold(默认 15)的对象晋升老年代。
动态年龄判定:Survivor 区中相同年龄对象总和超过该区一半时,大于等于该年龄的对象直接进入老年代。
空间分配担保:老年代最大连续空间不足,但大于历次晋升平均大小时,冒险执行 Minor GC;否则触发 Full GC。
TLAB(Thread-Local Allocation Buffer)
JVM 在新生代 Eden 区为每个线程分配一块私有区域(默认占 Eden 的 1%),用于快速分配小对象。TLAB 分配无需锁同步,提升效率。对象分配流程:
逃逸分析确定对象在堆分配。
若 TLAB 空间足够,直接分配并更新指针;否则重新申请 TLAB。
若 TLAB 仍不足,在 Eden 区加锁分配;Eden 不足则触发 Young GC,GC 后仍不足则分配到老年代。
对象内存分配方法
指针碰撞:堆内存规整时,通过指针移动分配内存(如 Serial、ParNew 收集器)。
空闲列表:堆内存碎片化时,维护可用内存列表分配(如 CMS 收集器)。
JVM 类加载过程
类生命周期包括:加载、验证、准备、解析、初始化、使用、卸载。
加载:通过全限定名获取字节流,生成 Class 对象。
验证:确保字节流合法,防止安全漏洞。
准备:为静态变量分配内存(默认值),实例变量在对象实例化时分配。
解析:将常量池符号引用转为直接引用(如指针)。
初始化:执行类构造器<clinit>(),初始化静态变量和代码块。
双亲委派模型
类加载器优先委托父加载器加载类,递归至顶层,父加载器无法加载时才由子类加载器尝试。该模型避免类重复加载,保护核心类(如java.lang.*)。破坏场景:JNDI、JDBC 等 SPI 机制通过线程上下文类加载器逆向加载第三方库,突破双亲委派限制。
JVM 锁优化与膨胀过程
自旋锁:获取锁失败时忙循环尝试,减少线程阻塞开销;自适应自旋根据前次自旋次数动态调整。
锁粗化:扩大加锁范围,避免频繁申请 / 释放锁(如循环内的同步块合并)。
锁消除:逃逸分析确认对象无竞争时,移除不必要的锁。
偏向锁:无竞争时锁关联线程,再次访问无需抢占,适用于单线程场景。
轻量级锁:竞争较小时通过 CAS 自旋获取锁,自旋超阈值升级为重量级锁。
重量级锁:依赖操作系统 MutexLock,阻塞线程,适用于高竞争场景。
i++ 操作的字节码指令
将 int 常量加载到操作数栈顶。
取出操作数栈顶值,存储到局部变量表 Slot 1。
从 Slot 1 取出值,放回操作数栈顶。
Slot 1 中的值加 1。
将操作数栈顶值存回 Slot 1(即 i 变量)。
JVM 性能监控工具
1. 命令行工具
jps:查看 JVM 进程及 LVMID。
jstat:监控类加载、内存、GC 数据(如jstat -gcutil <pid> 1000)。
jinfo:查看 / 调整 JVM 参数。
jmap:生成堆转储快照(jmap -dump:file=heapdump.hprof <pid>)。
jhat:分析堆快照,通过 HTTP 服务器查看结果。
jstack:生成线程快照,排查死锁或阻塞(jstack <pid> > thread.log)。
2. 可视化工具
JConsole:JDK 自带图形化监控工具,支持内存、线程、类加载监控。
VisualVM:功能强大,支持插件扩展,可分析性能瓶颈、内存泄漏。
JVM 常见参数
参数 | 说明 |
-Xms20M | 设置初始堆大小为 20M |
-Xmx20M | 设置最大堆大小为 20M(与 - Xms 相同可避免动态扩容) |
-verbose:gc | 输出 GC 详细日志 |
-Xss128k | 设置虚拟机栈大小为 128k(HotSpot 不区分本地方法栈,-Xoss 无效) |
-XX:MetaspaceSize=10M | 设置元空间初始大小为 10M(JDK 8+) |
-XX:+HeapDumpOnOutOfMemoryError | OOM 时生成堆转储快照 |
-XX:+UseG1GC | 启用 G1 收集器 |
-XX:MaxTenuringThreshold=1 | 对象年龄大于 1 时进入老年代 |
-XX:PretenureSizeThreshold=3145728 | 大于 3M 的对象直接进入老年代(单位:字节) |
-XX:+PrintGCDetails | 打印 GC 详细信息 |
JVM 调优目标与场景
调优时机
老年代内存持续接近最大值。
Full GC 频繁触发。
GC 停顿时间超过 1 秒。
应用抛出OutOfMemoryError。
本地缓存占用大量内存。
系统吞吐量或响应时间下降。
实战案例
1. Minor GC 频繁
原因:Eden 区过小,对象分配速率高。
优化:增大新生代空间(-Xmn)或调整-XX:SurvivorRatio,减少 GC 次数。
2. STW 过长
原因:老年代碎片多或大对象触发 Full GC。
优化:启用 G1 收集器(-XX:+UseG1GC),设置-XX:MaxGCPauseMillis=100控制停顿时间。
3. 元空间溢出(JDK 8+)
原因:动态类加载过多,元空间内存不足。
优化:调整-XX:MetaspaceSize和-XX:MaxMetaspaceSize,或优化类加载逻辑。
4. 外部命令导致性能下降
问题:频繁调用Runtime.exec()执行 Shell 脚本,创建进程开销大。
解决方案:改用 Java API 获取信息,避免外部进程调用。
5. Windows 虚拟内存导致 GC 停顿
问题:程序最小化时内存交换到磁盘,GC 时因页面文件恢复导致长时间停顿。
解决方案:添加参数
-Dsun.awt.keepWorkingSetOnMinimize=true,避免内存交换。
- 上一篇: JDK 内置实用工具:监视、故障排除
- 下一篇: 那个小白还没搞懂内存溢出,只能用案例说给他听了
猜你喜欢
- 2025-05-11 后端精选-Java问题排查工具清单(java后端测试工具)
- 2025-05-11 那个小白还没搞懂内存溢出,只能用案例说给他听了
- 2025-05-11 JDK 内置实用工具:监视、故障排除
- 2025-05-11 如何排查美国VPS服务器内存泄漏问题?
你 发表评论:
欢迎- 05-14JAVA程序员自救之路——Elasticsearch向量搜索
- 05-14探秘Java程序的“内存大爆炸”:JVM内存溢出问题排查
- 05-14Java 探秘:如何找出数组中重复的数字
- 05-14线上问题解决:java内存溢出问题分析,定位及解决
- 05-14Java虚拟机内存管理深度解读
- 05-14Java程序内存泄漏问题优化全攻略
- 05-14Jprofile解析dump文件使用详解
- 05-14Java中常见的内存泄漏场景解析
- 366℃用AI Agent治理微服务的复杂性问题|QCon
- 358℃初次使用IntelliJ IDEA新建Maven项目
- 352℃手把手教程「JavaWeb」优雅的SpringMvc+Mybatis整合之路
- 351℃Maven技术方案最全手册(mavena)
- 348℃安利Touch Bar 专属应用,让闲置的Touch Bar活跃起来!
- 346℃InfoQ 2024 年趋势报告:架构篇(infoq+2024+年趋势报告:架构篇分析)
- 343℃IntelliJ IDEA 2018版本和2022版本创建 Maven 项目对比
- 342℃从头搭建 IntelliJ IDEA 环境(intellij idea建包)
- 最近发表
- 标签列表
-
- powershellfor (55)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)