【JVM】一文掌握JVM垃圾回收机制
作为Java程序员,除了业务逻辑以外,随着更深入的了解,都无法避免的会接触到JVM以及垃圾回收相关知识。JVM调优是一个听起来很可怕,实际上很简单的事。
感到可怕,是因为垃圾回收相关机制都在JVM的C++层实现,我们在Java开发中看不见摸不着;而实际很简单,是因为它说到底,也只是JVM替我们实现的垃圾对象回收机制,也是普通的程序代码,只要理解了垃圾回收器的底层设计思想,掌握JVM调优并非难事!
一、JVM内存模型
元数据区:JDK8之前是方法区。存放虚拟机加载的:类型信息,域(Field)信息,方法(Method)信息,常量,静态变量,即时编译器编译后的代码缓存
虚拟机栈:虚拟机栈中保存了每一次方法调用的栈帧信息,栈帧中包含以下信息:
- 局部变量表:保存函数 (即方法) 的局部变量
- 操作数栈:保存计算过程中的结果,即临时变量
- 动态链接:指向方法区的运行时常量池。字节码中的方法调用指令以常量池中指向方法的符号引用为参数。
- 方法的返回地址
本地方法栈:和虚拟机栈功能上类似,它管理了native方法的一些执行细节,而虚拟机栈管理的是Java方法的执行细节。
程序计数器:程序计数器记录线程执行的字节码行号,如果当前线程正在运行native方法则为空。每个线程都有自己的计数器
堆:JVM中产生的实例对象的存储位置
所谓的垃圾回收,主要就是回收JVM中堆内存的区域
二、垃圾定义
- 引用计数(ReferenceCount):存在循环引用的问题,漏掉循环引用的垃圾
- 根可达算法(RootSearching):判断对象是否可通过引用寻到JVM的根节点,不能则是垃圾
三、垃圾回收算法
- 标记清除(mark sweep) - 位置不连续 产生碎片 效率偏低(两遍扫描)
- 拷贝算法 (copying) - 没有碎片,浪费空间
- 标记压缩(mark compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整)
四、垃圾回收器
通过以上三种算法的排列组合,产生了各种各样的垃圾回收器
堆内存逻辑分区
常见垃圾回收器
• Serial:单线程STW垃圾回收器,工作在年轻代。采用拷贝算法
• Serial Old:单线程STW垃圾回收器,工作在老年代。采用标记清除加压缩算法
• Parallel Scavenge:并行垃圾回收,工作在年轻代。采用拷贝算法
• Parallel Old:并行垃圾回收,工作在老年代。采用标记清除加压缩算法
• ParNew:并行垃圾回收,工作在年轻代。专门配合CMS使用
• CMS(Concurrent Mark-Sweep):并发标记清除,工作在老年代,采用标记清除算法。
• G1(Garbage First):垃圾优先算法,采用拷贝算法
• ZGC(Z Garbage Collector):一种可伸缩的低延迟垃圾回收器,旨在处理TB级别的堆,同时保持低毫秒级别的停顿时间。它通过使用读屏障和染色指针来实现这一点,并且在垃圾回收过程中几乎不需要暂停应用线程
• Shenandoah GC:是一种旨在实现低停顿时间的垃圾回收器,它通过并发的方式来回收内存。Shenandoah的目标是减少停顿时间,而不是优化吞吐量,适用于需要大内存和低延迟的应用
垃圾升级过程
- 新创建对象产生在eden区
- ygc触发,把eden区和s0区不是垃圾的对象复制到s1区,并对非垃圾对象的头部的分代年龄加一,然后清除eden区和s0区
- ygc触发,把eden区和s1区不是垃圾的对象复制到s0区,并对非垃圾对象的头部的分代年龄加一,然后清除eden区和s1区
- 当对象头记录的分代年龄达到15(默认最大分代年龄)时,jvm将把他从年轻代升级到老年代
eden区和s0、s1的默认比例是8:1:1,可通过参数-XX:SurvivorRatio配置
对象头的年龄可通过-XX:MaxTenuringThreshold参数配置,但由于对象头中只用4个比特位存储分代年龄,因此它的区间是0-15
CMS垃圾回收器
CMS是用于回收老年代的垃圾回收器,它采用的是标记清除算法。CMS的诞生的目的在于提供在多核环境下的并发处理中大型堆(MB~GB)垃圾的能力
特点
- 并发收集:CMS的主要特点是它允许垃圾回收线程与应用程序线程同时运行。这减少了应用程序的停顿时间,特别是在长时间运行的老年代垃圾回收过程中。
- 低停顿时间:由于并发执行,CMS旨在减少垃圾回收引起的停顿时间,这对于延迟敏感的应用程序非常重要。
- 存在内存碎片:CMS通常不执行堆压缩,这意味着它不会重新安排存活对象来消除空闲空间之间的碎片。这可以减少停顿时间,但可能导致更多的内存碎片。
- CPU资源密集型:CMS需要更多的CPU资源来执行并发的垃圾回收。在多核处理器上,这通常不是问题,但在CPU资源受限的环境中,可能会影响应用程序的性能。
- 并发模式失败:在极端情况下,如果老年代在CMS回收过程中被填满,会发生“并发模式失败”。这时,JVM会退回到传统的完全停顿式垃圾回收,以清理老年代。
- 适用于中到大型堆:CMS适合于中到大型的堆,尤其是在有足够CPU资源和对停顿时间敏感的应用场景中。
- 需要调优:为了获得最佳性能,CMS可能需要通过JVM参数进行调优,如设置堆的大小、老年代的大小、并发线程数等。
垃圾回收过程
- 初始标记:找到根上的垃圾,会有非常短暂的STW
- 并发标记:标记垃圾,这一步可能产生漏标(扫描完不是垃圾之后,突然失去引用变成了垃圾),也可能产生多标(扫描完事垃圾之后,突然重新被引用变成不是垃圾)
- 重新标记:重新标记的目的是纠正上一步所产生的错误标记,会有时间不算很长的STW
- 并发清理:清理前面步骤所标记出来的垃圾
三色标记算法
作用于并发标记阶段
对象标记为黑白灰三个颜色,记录当前扫描标记的位置。
- 黑色:自己已经被标记,自己的子引用也都标记完成
- 白色:没有遍历到的节点
- 灰色:自己已经被标记,自己的子引用还没被全部标记完成
三色标记的bug
由于并发标记是与用户线程并行的,所以在并发标记的过程中对象的引用是可能发生变化的,所以可能会产生多标和漏标。并且重新标记为了减少STW的时间不会再标记黑色对象,而是扫描灰色对象的直接引用
- 多标:会导致产生浮动垃圾,需要在下一次判断引用再回收,无大碍
- 漏标:会导致不应该被回收的对象被回收,问题严重
如上图:在并发标记的过程中,同时产生这两种情况时就会发生回收错误问题:A和C断开了引用,A又引用了D。
- 对于对象C:应该回收的对象现在是黑色,留了下来
- 对于对象D:被引用了但还是白色,由于重新标记时不会再扫描黑色对象,这样会导致对象D被当作垃圾而回收,产生严重bug
CMS对于三色标记的错标处理
CMS的处理方式是Increment Updater(增量更新),即当已经被扫描完的黑色对象如果产生了新的引用,则把自己标记为灰色,等待下次扫描重新标记。
但在上述的多标案例中,CMS存在却依然并发标记Bug,如下时序图
当两个垃圾回收线程m1和m3加上一个业务线程m2同时标记一个对象时,m3认为应该标灰,但m1认为应该标黑,如果最终m1的标记覆盖了m3的标记,那么对象的颜色标记错误,它下面新增的引用也不会被扫描到
CMS对于这个严重的bug的解决方案是,在重新标记阶段重新扫描时,必须从头扫描一遍,这样就增加了STW的时间
G1垃圾回收器
G1(Garbage-First)垃圾回收器是Java虚拟机(JVM)的一个高级垃圾回收器,旨在为具有大内存的多处理器机器提供高吞吐量和低延迟。G1垃圾回收器的主要特点包括:
特点
- 分区堆结构:G1将堆内存分割成多个大小相等的区域(Region),这些区域可以被划分为Eden区、Survivor区或Old区。这种分区方法有助于更有效地管理堆空间。
- 并发和并行处理:G1结合了并发和并行的垃圾回收机制,以优化性能和减少停顿时间。
- 可预测的停顿时间:G1的一个关键目标是提供可预测的停顿时间,允许用户指定期望的停顿时间目标(例如,不超过50毫秒),G1将尽量在这个时间范围内完成垃圾回收。
- 增量式清理:G1通过逐步清理堆中的区域来管理垃圾回收,这有助于控制停顿时间。
- 记忆集(RSet):G1使用记忆集来跟踪跨区域引用的对象,这有助于在垃圾回收时快速确定哪些对象是存活的。
- 混合收集:G1可以同时回收Young和Old区域。在进行混合收集时,G1会根据需要和停顿时间目标选择性地回收一部分Old区域。
- 高效的大对象处理:G1能够更有效地处理大对象,因为它可以跨多个区域分配这些对象。
- 自适应调整:G1会根据应用程序的行为和指定的停顿时间目标自动调整堆占用和回收策略。
- 适用于大堆:G1特别适合于大堆(多GB)的应用,因为它能够更好地管理大内存并保持合理的停顿时间。
分区算法(Region)
G1的物理分区从分代变成了分区(Region),逻辑上分代,物理上则取消了分代,把堆整体划分成了多个(2048)相同大小的小格子(Region)
其中,每个Region的大小可通过-XX:G1HeapRegionSize设定,取值范围为1-32MB,且必须为2的N次幂,即只能为2,4,8,16,32这五个数
每一个Region都可以根据需要充当新生代的Eden区、S区(G1取消了S0和S1,只使用一个Survivor区)或者老年代。在一般的垃圾收集中对于堆中的大对象,默认直接会被分配到老年代,但是如果它是一个短期存在的大对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放大对象。如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储。为了能找到连续的H区,有时候不得不启动Full GC。 G1的大多数行为都把H区作为老年代的一部分来看待。当一个对象的大小超过了一个Region容量的一半,即被认为是大对象。
虽然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,而是一系列区域(不需要连续,逻辑连续即可)的动态集合。由于G1这种基于Region回收的方式,可以预测停顿时间。G1会根据每个Region里面垃圾“价值”的大小,在后台维护一个优先级列表,每次根据用户设定的允许收集停顿的时间(-XX:MaxGCPauseMillis,默认为200毫秒)优先处理价值收益最大的Region。
垃圾回收过程
G1采用的复制(copying)算法进行回收
- 初始标记:仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能够在Region上正确的分配对象。这个阶段需要STW,耗时很短,而且是借用MinorGC(上一轮垃圾回收时触发GC)时候同步完成的。
- 并发标记:从GC Roots 开始对堆中的对象进行可达性分析,递归扫描整个堆里的对象,这个过程耗时较长,但是是与用户线程并发执行的。对象扫描完之后还需要重新处理STAB记录下的在并发时有引用变动的对象。
- 最终标记:这个阶段也需要STW,用于处理并发阶段结束后仍然遗留下来的最后少量的STAB记录。
- 筛选回收:负责更新Region的统计数据,对各个Region的回收价值和成本排序,根据用户期望的停顿时间来执行回收计划,然后把决定回收的Region里的存活对象复制到空的Region,然后清空旧Region的空间。由于涉及到对象的移动,所以这个阶段也是需要STW的。
从上述可以看出,除了并发标记,其他阶段都是需要STW的,G1收集器不单单是追求低延迟的收集器,也衡量了吞吐量,所以在延迟和吞吐量之间做了一个权衡。
G1对于三色标记的错标处理
从上述过程可以看出G1的处理方式是SATB(snapshot at the begining),即在并发标记中,如果出现引用的变更,G1的垃圾回收器会记录在SATB中,每次线程切回来进行垃圾回收时,先读取SATB中的记录。
RememberedSet
简称RSet,记录了其他Region的对象到本Region的引用,使得垃圾回收器不需要扫描整个堆找到谁引用了当前分区的对象,只需扫描RSet即可
更多技术干货,欢迎关注我!
【JVM】一文掌握JVM垃圾回收机制的更多相关文章
- 巩固java(二)----JVM堆内存结构及垃圾回收机制
前言: 我们在运行程序时,有时会碰到内存溢出(OutOfMemoryError)的问题,为了解决这种问题,我们有必要了解JVM的内存结构和垃圾回收机制. 正文: 1.JVM堆内存结构 ...
- JVM性能优化--Java的垃圾回收机制
一.Java内存结构 1.Java堆(Java Heap) java堆是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建.此内存区域的唯一目的就是存放对象实例 ...
- Java虚拟机垃圾回收机制
在Java虚拟机中,对象和数组的内存都是在堆中分配的,垃圾收集器主要回收的内存就是再堆内存中.如果在Java程序运行过程中,动态创建的对象或者数组没有及时得到回收,持续积累,最终堆内存就会被占满,导致 ...
- JVM的垃圾回收机制详解和调优
JVM的垃圾回收机制详解和调优 gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都 ...
- JVM虚拟机-垃圾回收机制与垃圾收集器概述
目录 前言 什么是垃圾回收 垃圾回收的区域 垃圾回收机制 流程 怎么判断对象已经死亡 引用计数法 可达性分析算法 不可达的对象并非一定会回收 关于引用 强引用(StrongReference) 软引用 ...
- JVM垃圾回收机制总结:调优方法
转载: JVM垃圾回收机制总结:调优方法 JVM 优化经验总结 JVM 垃圾回收器工作原理及使用实例介绍
- JVM的生命周期、体系结构、内存管理和垃圾回收机制
一.JVM的生命周期 JVM实例:一个独立运行的java程序,是进程级别 JVM执行引擎:用户运行程序的线程,是JVM实例的一部分 JVM实例的诞生 当启动一个java程序时.一个JVM实例就诞生了, ...
- java JVM垃圾回收机制
Java语言出来之前,大家都在拼命的写C或者C++的程序,而此时存在一个很大的矛盾,C++等语言创建对象要不断的去开辟空间,不用的时候有需要不断的去释放控件,既要写构造函数,又要写析构函数,很多时候都 ...
- JVM垃圾回收机制总结(3) :按代垃圾收集器
为什么要分代 分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的 . 因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率. 在Java程序运行的过程中,会产生大量的对 ...
- JVM内存模型及垃圾回收机制
http://blog.csdn.net/zhangpengju999/article/details/11773183 JVM垃圾回收机制 分代垃圾回收 不同的对象生命周期不同.与业务信息有关的对象 ...
随机推荐
- .NET使用quartz+topshelf实现定时执行任务调度服务
一.项目开发 1.新建控制台应用(.NET Framework) 2.配置新项目,自行修改项目名称.位置和框架(建议使用.NET Framework4.5以上版本) 创建好的项目目录如下: 3.右键引 ...
- 云原生(docker jenkins k8s)docker篇
docker (1)架构 ● Docker_Host: ○ 安装Docker的主机 ● Docker Daemon: ○ 运行在Docker主机上的Docker后台进程 ● Client: ○ 操作D ...
- Solution -「POJ 1322」Chocolate
Description Link. 包里有无穷多个巧克力,巧克力有 \(c\) 种颜色,每次从包里拿出不同颜色巧克力的概率都是相等的,桌面的巧克力不允许颜色相同,若某次拿出的巧克力与桌上的巧克力颜色相 ...
- Teamcenter RAC 开发之《Excel模版导出》
背景 在做 Teamcenter RAC客制化表单后,TMD肯定有一个需求要导出表单,毕竟所谓的客制化表单就是从纸质表单中出来的,那么写代码必不可少......... 那么问题来了,对于一个Excel ...
- MUH and Cube Walls 题解
MUH and Cube Walls 前言 怎么题解区同质化这么严重,16 篇题解全是 差分 + KMP,就没有人写别的做法吗. (好吧其实是我一开始没想到差分才有了这么多奇怪做法) 题目大意 给定两 ...
- CF1401B [Ternary Sequence]
Problem 题目简述 两个序列 \(A, B\).这两个序列都是由 \(0,1,2\) 这三个数构成. \(x_1,y_1,z_1\) 和 \(x_2,y_2,z_2\) 分别代表 \(A\) 序 ...
- 这次弄一下maven 多模块项目,用vscode新建一下,便于管理项目
首先 创建一个mvn项目, 直接在命令行执行, 原型生成: mvn archetype:generate 选一个maven quick start的template, 然后删除src和target文件 ...
- Go类型嵌入介绍和使用类型嵌入模拟实现“继承”
Go类型嵌入介绍和使用类型嵌入模拟实现"继承" 目录 Go类型嵌入介绍和使用类型嵌入模拟实现"继承" 一.独立的自定义类型 二.继承 三.类型嵌入 3.1 什么 ...
- 一篇适合躺收藏夹的 Nexus3 搭建 NuGet&Docker 私有库的安装使用总结
前言 Nexus 是支持 Nuget.Docker.Npm 等多种包的仓库管理器,可用做私有包的存储分发,缓存官方包.本篇将手把手教学使用 Nexus 搭建自己的 NuGe t& Docker ...
- 【pwn】[SWPUCTF 2022 新生赛]InfoPrinter--格式化字符串漏洞,got表劫持,data段修改
下载附件,checksec检查程序保护情况: No RELRO,说明got表可修改 接下来看主程序: 函数逻辑还是比较简单,14行出现格式化字符串漏洞,配合pwntools的fmtstr_payloa ...