《深入理解Java虚拟机》第三章读书笔记(三)——经典垃圾回收器
一丶概述
上图展示了 经典的垃圾回收器,其中Serial,ParNew,Parallel Scavenge(途中的Parallel)
作用在新生代Serial Old CMS,Parallel Old
作用在老年代,这些垃圾回收器颜色相同表示通常搭配使用。G1,ZGC,Shenandoah
垃圾收集器则抛弃了分代收集理论作用于整堆。下面将介绍这些垃圾回收器
二丶Serial 和 Serial Old
Serial
是作用在新生代
的,使用标记复制算法
且单线程
进行垃圾收集的垃圾回收器(单线程意味着:回收垃圾的时候使用单个线程,并且回收垃圾的时候工作线程也停止
)。它是客户端模式下默认
的垃圾收集器,优点点在于简单高效,对于内存资源受限的环境,它是所有收集器里额外内存消耗最小的,并且没用线程切换的开销,因此在单核CPU下Serial表现甚至优于多核CPU
。
Serial Old
是Serial
的老年代版本,使用标记整理算法,除了和Serial搭配使用之外,它还是CMS老年代垃圾回收失败时的后备预案
- -XX:+UseSerialGC :让虚拟机使用
Serial+Serial Old
进行垃圾回收 - -XX:SurvivorRatio:控制Survivor 和 Eden的比值,如-XX:SurvivorRatio=8 表示Eden:Survivor=8:1
三丶ParNew
ParNew 可用认为是Serial的并行版本,作用于新生代,使用标记复制算法
,它是激活CMS垃圾回收器之后默认的新生代垃圾回收器。
ParNew在单核CPU处理器环境中,不会比Serial更高效,因为它存在线程交互的开销。使用-XX:ParallelGCThreads=n
限制垃圾收集的线程数
使用 -XX:+UseParNewGC
指定使用ParNew + Serial Old
的组合,二者配合工作如下图。
四丶Parallel Scavenge
Parallel Scavenge
和ParNew 类似,作用于新生代,使用标记复制算法
。但是它是专注于吞吐量的垃圾收集器。(吞吐量 = 用户代码运行时间 / 处理器总消耗时间(用户代码时间+GC时间)
)
-XX:+UseParallelGC
虚拟机在Server模式下的默认值,开启后,使用 Parallel Scavenge + Serial Old的组合-XX:MaxGCPauseMillis
=n 收集器尽可能保证单次内存回收停顿的时间不超过这个值,但是并不保证不超过该值。(如果设置太小将导致Minor GC频繁)-XX:GCTimeRatio=n
设置吞吐量的大小,取值范围0-100,假设GCTimeRatio
的值为 n,那么系统将花费不超过1/(1+n)
的时间用于垃圾收集-XX:+UseAdaptiveSizePolicy
开启后,无需人工指定新生代的大小(-Xmn
)、 Eden和Survivor的比例(-XX:SurvivorRatio
)以及晋升老年代对象的年龄(-XX:PretenureSizeThreshold
)等参数,收集器会根据当前系统的运行情况自动调整
五丶Parallel Old
Parallel Old
是Parallel Scavenge的老年代版本,多线程,使用标记整理算法
.-XX:+UseParallelGC
使用 Parallel Scavenge + Serial Old
的搭配方式,并不能重复发挥服务器的多处理器并行处理能力。Parallel Old 出现后注重吞吐量的场景可用使用-XX:+UseParallelOldGC
选择Parallel Scavenge + Parallel Old
进行搭配使用
六丶CMS
CMS是一种以获取最短回收停顿时间为目标收集器
,使用标记清除算法回收老年代
。适合在关注服务响应速度
,系统停顿时间
的场景中使用,如B/S 系统。CMS的优势在于它使用了三色标记算法,实现了垃圾回收和用户线程的并行执行(初始标记和并发重新标记还是需要Stop The World)
1.CMS回收流程
初始标记
指的是寻找所有被 GCRoots 引用的对象,该阶段需要Stop the World ,这个步骤仅仅只是标记一下 GC Roots 能直接关联到的对象,并不需要做整个引用的扫描,因此速度很快。
并发标记
指的是对「初始标记阶段」标记的对象进行整个引用链的扫描,该阶段不需要Stop the World。 对整个引用链做扫描需要花费非常多的时间,因此通过
垃圾回收线程与用户线程并发执行,可以降低垃圾回收的时间
。这也是 CMS 垃圾回收器能极大降低 GC 停顿时间的核心原因,但这也带来了一些问题,即:并发标记的时候,引用可能发生变化,因此可能出现错杀(是垃圾的对象没用被标记为垃圾,产生浮动垃圾)
,和错标(不是垃圾的对象被标记为垃圾
的问题重新标记
指的是对并发标记阶段出现的问题进行校正,该阶段需要Stop the World。 这一阶段解决并发标记阶段发生错标的问题。
并发清除
指的是将标记为垃圾的对象进行清除,该阶段不需要Stop the World。垃圾回收线程和用户线程并发进行。(这一步使用标记清除算法,所有可以和用户线程并行,但是标记清除会产生较多的内存碎片)
2.CMS 优点
并发收集,低停顿
将原本的标记信息,分为了初始标记,并发标记,重新标记,其中最为耗时的并发标记可以和用户线程并行,从而提高了垃圾回收的效率。垃圾回收的阶段(并发清除)也可以和用户线程并行,实现了垃圾回收的低停顿。
3.CMS 缺点
对 CPU 资源消耗较大
虽然使用多线程进行垃圾收集提升效率,但是也真是由于多线程垃圾收集流程会占用一些处理器的计算能力而导致应用程序变慢,降低吞吐量。
默认情况下 CMS 启用的垃圾回收线程数是
(CPU数量 + 3)/4
,当 CPU 数量越大时,启用的垃圾回收线程数占比就越小。但如果 CPU 数量不足四个的时候,垃圾回收线程占用就达到了 50%,也就是说需要拿 50% 的 CPU 时间来进行垃圾回收。这就会极大地降低系统的吞吐量,这是让人无法接受的情况。
无法处理浮动垃圾。 由于 CMS 并发标记阶段会发生错标的情况,因此会有一些本该回收的垃圾对象无法被回收。此外在 CMS 进行并发清理的时候,用户线程同时在运行,也会产生一些浮动垃圾。因此对于 CMS 回收器来说,其需要留出一些空间给这些浮动垃圾存储,等到下一次垃圾回收才能进行清理。
可以通过
-XX:CMSInitiatingOccupancyFraction
参数调节老年代空间使用多少之后触发CMS进行老年代的回收。如果在 CMS 运行期间发现预留的内存无法满足程序需要,就会提示
Concurrent Mode Failure
错误。此时虚拟机采用后备方案:临时启用 Serial Old 回收器来重新进行老年代的垃圾回收,这时候 Stop the World 的时间可能就会很长了
。产生空间碎片。 由于 CMS 是基于
标记-清除
算法实现的回收器,因此其会产生很多空间碎片,这会导致给大对象分配的时候很麻烦,会提前触发 Full GC
。可以使用-XX:+UseCMSCompactAtFullCollection(默认是打开的)
参数来让无法分配连续内存给大对象的时候,让FullGC触发的时候进行老年代的整理工作。该参数通常和
-XX:CMSFullGCsBeforeCompaction
一起使用,-XX:CMSFullGCsBeforeCompaction
可以控制CMS发生多少次不整理的FullGC后,下一次FullGC进行老年代的整理。
七丶G1
Garbage First 简称G,它开创了收集器面向局部收集,和基于Region的内存布局。
1.停顿模型
停顿模型:支持在长度为M毫秒的时间内,消耗在垃圾回收上的时间大概不超过N毫秒的目标
G1如何实现这个目标:
摒弃了面向整个区域(整个新生代,老年代,堆)进行垃圾回收的樊笼。而是面向堆内任何部分来组成Collection Set(CSet)进行回收,而不是看它属于哪个分代,而是看哪块内存中存放的垃圾数量多,回收收益最大,这便是G1的Mixed GC。支撑整个目标实现的是G1基于Rigion的堆内存布局
2.基于Region的堆内存布局
可以看出G1也是遵循分代收集理论
的,但是堆内存布局不在坚持固定大小以及固定数量的分代区域划分
,而是把堆分为若干个Region,每一个Region可以是Eden,Survivor,Old中一种。其中还存在Humongous
用于存储大对象,如果一个对象超过Region大小的一半,那么视为大对象,G1将这部分区域视作老年代。
Region的大小可以通过-XX:G1HeapRegionSize
进行设置,必须是2的幂次方,且介于1MB~32MB。超过一个Region大小的大对象讲分配在N个连续的Humongous
中。
G1每次回收都是回收若干个Region大小的空间
,这也可以避免整堆收集
,并且会使用一个优先级列表来记录和跟踪每个Region中垃圾堆积的价值,价值即回收获得的空间大小,和回收所需的时间的经验值。每次垃圾回收都根据-XX:MaxGCPauseMillis(默认200ms)
指定的停顿时间,选择回收价值最大的Region。
3. G1 如何解决跨代引用垃圾回收的问题
Region里面存在跨Region引用的问题依据是使用记忆集的思路去解决的。但是G1的记忆集更为复杂,每一个Region存在一个记忆集,这些记忆集会记录其他Region指向自己的指针,并标记这些指针位于哪些卡页的范围内。
这种结构占用更大的内存,因为如果堆内存太小不太适合使用G1垃圾收集器。
4.G1 如何解决并发标记阶段,用户线程更改引用关系造成的问题
G1使用原始快照的方式解决此问题
当灰色对象要删除指向白色对象的引用关系时,就将删除的引用记录下来,并发扫描结束后,再将这些记录过引用关系的灰色对象为根,重新扫描一次。
此外G1还设计了两个指针称为TAMS(Top at mark start)
用于在并发回收阶段,让用户线程在此区域分配内存给新对象。G1默认认为这个区域的对象是存活的。
5. G1 垃圾回收流程
初始标记
标记了从GC Roots可以直接关联可达的对象。需要Stop the world
并发标记
和用户线程并发执行,从GC Roots开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象、
最终标记
需要Stop the world,处理并发标记阶段,使用原始快照SATB记录的对象。
筛选回收
更新Region统计信息,对各个Region的对象回收价值和成本进行排序,然后根据用户期望的停止时间来指定回收计划,然后选择多个Region组成
Collection Set(CSet)
来进行回收,将回收这部分Region中存活的对象复制到空Region中,再清理整个旧Region的全部空间,这部分需要移动对象,依旧需要Stop the world,由多个线程并行完成。
6.G1 和CMS对比
6.1 G1优于CMS的点
- 可以指定最大停顿时间
- 可以按收益动态确定回收集
- 筛选回收使用标记复制算法,不会产生内存碎片,可以避免无法分配连续内存而进行下一次收集的情况的发生
6.2 CMS 由于G1的点
CMS的记忆集只有一份,占用内存小,但是G1每一个Region都有记忆集,所有G1占用更多内存
因此小内存机器上CMS更适合,堆内存足够大的适合使用G1也是不错的选择。
CMS使用写后屏障维护记忆集,并且是直接同步操作(更改引用的同时进行更新卡表)
而G1为了实现原始快照,还需要使用写前屏障,因此G1使用消息队列进行异步处理 记忆集的更新
《深入理解Java虚拟机》第三章读书笔记(三)——经典垃圾回收器的更多相关文章
- 《深入理解java虚拟机-高效并发》读书笔记
Java内存模型与线程 概述 多任务处理在现代计算机操作系统中几乎已是一项必备的功能,多任务运行是压榨手段,就如windows一样,我们使劲的压榨它运行多个任务,俱要high又要耍.并发则是另外一种更 ...
- 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化
<深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...
- 深入理解java虚拟机-第13章-线程安全与锁优化
第十三章 线程安全与锁优化 线程安全 java语言中的线程安全 1 不可变.Immutable 的对象一定是线程安全的 2 绝对线程安全 一个类要达到不管运行时环境如何,调用者都不需要额外的同步措施, ...
- 《深入理解java虚拟机》第3版笔记3
第3章 垃圾收集器与内存分配策略 可达性分析算法 在Java技术体系里面,固定可作为GC Roots的对象包括以下几种: 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使 ...
- 深入理解java虚拟机_第二章_读书笔记
1.本章内容目录: 概述 运行时数据区域 程序计数器 java虚拟机栈 本地方法栈 java堆 方法区 运行时常量池 直接内存 HotSpot虚拟机对象探秘 对象的创建 对象的内存布局 对象的访问定位 ...
- 深入理解Java虚拟机-第1章-走进Java-读书笔记
第 1 章 走近 Java 前言 Java 的技术体系主要是由支撑 Java 程序运行的虚拟机.为各开发领域提供接口支持的 Java API.Java 编程语言及许许多多的第三方 Java 框架(如 ...
- 《深入理解java虚拟机》第二章 Java内存区域与内存溢出异常
第二章 Java内存区域与内存溢出异常 2.2 运行时数据区域
- 深入理解java虚拟机-第六章
第6章 类文件 6.3 Class类文件的结构 Class文件是一组以8位字节为基础单位的二进制流. Class文件格式采用一种类似C语言结构伪结构存储数据,这种伪结构中只有两种数据类型:无符号数和表 ...
- 《实战Java高并发程序设计》读书笔记三
第三章 JDK并发包 1.同步控制 重入锁:重入锁使用java.util.concurrent.locks.ReentrantLock类来实现,这种锁可以反复使用所以叫重入锁. 重入锁和synchro ...
- Linux内核分析第三章读书笔记
第三章 进程管理 3.1 进程 进程就是处于执行期的程序 进程就是正在执行的程序代码的实时结果 线程:在进程中活动的对象.每个线程都拥有一个独立的程序计数器.进程栈和一组进程寄存器. 内核调度的对象是 ...
随机推荐
- 51单片机-独立按键控制led矩阵的左移和右移
51单片机学习 独立按键 控制led灯光矩阵的左移和右移 开发板采用的是普中的A2学习开发板,具体的代码如下: typedef unsigned int u16; void delay(u16 tim ...
- cordon节点,drain驱逐节点,delete 节点
目录 一.系统环境 二.前言 三.cordon节点 3.1 cordon节点概览 3.2 cordon节点 3.3 uncordon节点 四.drain节点 4.1 drain节点概览 4.2 dra ...
- day12-Servlet02
Servlet02 6.GET和POST请求的分发处理 开发Servlet,通常编写doGet,doPost方法.来对表单的get和post请求进行分发处理 例子 在web文件夹下面创建一个html页 ...
- 统一的开发平台.NET 7正式发布
在 2020 年规划的.NET 5功能终于在.NET 7 完成了,为微软和社区一起为多年来将不同的开发产品统一起来的努力加冕,未来只有一个.NET, 回顾.NET 20年,从.NET Framewo ...
- vue 数组更新(push【可用】,$set,slice,filter,map【都属于浅拷贝】)问题
this.$axios.post('https://....php',this.$qs.stringify({ user: 'suess' })) .then(res => { this.dat ...
- Go语言核心36讲21
提到Go语言中的错误处理,我们其实已经在前面接触过几次了. 比如,我们声明过error类型的变量err,也调用过errors包中的New函数.今天,我会用这篇文章为你梳理Go语言错误处理的相关知识,同 ...
- chronyd为隔离网络设置时间同步
参考链接:https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_basic ...
- .net core/5/6/7中WPF如何优雅的开始开发
WPF是微软的.net平台中的一个桌面客户端应用程序框架,经常用于企业开发windows桌面客户端,广泛应用于中小企业快速开发一款工具,本人也是比较喜欢利用WPF开发一些小工具. 目录 知名案例 .n ...
- USB限流,短路保护芯片IC
USB口的输出电压一般是5V,在一些电源中,由于总电源5V是一个很大的总电源,再分别出很多路输出负载出来,例如5V10A,分成4个USB输出口,如果没加其他限流和保护的话,任意一个USB口的输出电流都 ...
- ORM常用字段与参数(自定义字段)
目录 一:orm中常用字段及参数 1.说明 2.自定义字段使用 3.ORM字段参数 一:orm中常用字段及参数 1.说明 id字段是自动添加的,如果你想要指定自定义主键,只需在其中一个字段中指定pri ...