Java中七大垃圾回收器
# Java中七大垃圾回收器
原文链接:https://blog.csdn.net/weixin_43784989/article/details/101674169 (opens new window)
在Java中,垃圾回收 (opens new window)是JVM最常见的工作,也是保证系统能稳定运行的保障之一,常见的垃圾回收算法有两种:分代回收和分区回收,他们各有优缺。当然回收垃圾不可能空手套白狼,所以下面就介绍一下七种垃圾回收器:
下图向我们展示了JDK1.8之后,垃圾回收器的使用场景:
新生代 | 年老代 |
---|---|
Serial | CMS |
ParNew | ParallelOld |
ParallelSacvenge | SerialOld(MSC) |
还有一个最特殊的G1收集器,他会在收集的时候按情况选择收集老年代或新生代;
# 1.Serial垃圾回收器(单线程、复制算法)
Serial垃圾收集器是最基本、发展时间最长的垃圾收集器,在JDK1.3之前新生代唯一的一个垃圾收集器。他不但只会用一个线程去收集垃圾,在收集垃圾的时候其他的所有工作线程必须停止,即会发生Stop the World现象,直到垃圾回收结束。
对于Stop the world,他会在垃圾回收开始时停掉其他所有的线程,只供垃圾回收器回收使用,这样对于系统尤其是高并发系统来说就是一个噩梦;
从JDK1.3开始,从 Serial——>Paralle——>CMS——>G1 ,不断改进收集器回收策略,减少STW的停顿时间,但仍然无法完全消除;
可以参考 https://blog.csdn.net/tjiyu/article/details/53982412 (opens new window)
优点 | 缺点 |
---|---|
简单高效,对于限定单个CPU的环境来说,Serial垃圾收集器没有线程交互(交换)开销,可以获得最高的单线程手机效率 | 会发生SWT现象 |
# 2.ParNew垃圾回收器(Serial+多线程)
ParNew是Serial收集器的多线程版本,也使用复制算法,除了使用多线程对垃圾进行收集之外,他和Serial几乎没有什么区别也会发生Stop The World现象。多用于Server模式下。也可以与下文的CMS收集器配合使用
# 3.Parallel Scavange 收集器(多线程复制算法)
ParallelScavange垃圾回收器是一个新生代的垃圾回收器,同样使用复制算法,也是一个多线程的垃圾回收器,他重点关注的是程序的吞吐量问题;
吞吐量 = CPU运行用户的代码时间 / CPU总的消耗时间 = 运行用户代码时间 / (运行用户代码时间+垃圾回收时间)
它的调优方式有:
—XX:MaxGCPauseMillis
控制最大垃圾回收时间,大于0ms;—XX:GCTimeRatio
设置垃圾回收时间占总时间的比率,0<n<100的整数;—XX:+UseAdptiveSizePolicy
不用手动指定一些细节的调整(-Xmn 、-XX:SurvivorRation、-XX:PetenureSizeThreshold),JVM会根据当前系统的运行情况收集监测性能,自动调节参数
以上都为新生代的垃圾回收器,下面来看看老年代的垃圾回收器
# 4.Serial Old垃圾回收器(单线程标记整理算法)
Serial Old是Serial的老年代收集版本,他同样是个单线程收集器,使用标记-整理算法,这个垃圾收集器主要运行在客户端的JVM (opens new window)中默认的老年代垃圾回收器。当然也会发生STW(Stop The World)现象
主要应用场景有:
- 用于Client模式
- 用于Server模式时
1.在JDK1.5之前,与ParallelScavenge收集器搭配使用,
2.作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用;
# 5.Parallel Old收集器(多线程标记整理算法)
在JDK1.5之前新生代使用ParallelScavenge只能与Serial Old搭配使用,只能保证新生代的吞吐量,无法保证老年代的吞吐量。从JDK1.6开始,ParallelOld成为Parallel Scavenge 的老年代收集器版本;
在注重吞吐量的前提下,使用Parallel Scanvenge + Parallel Old作为组合;在多核CPU且对吞吐量及其敏感的Server系统中推荐使用;
# 6.CMS收集器(多线程标记清除收集器)
Concurrent Mark Sweep (CMS)垃圾收集器时针对老年代的一个并发线程的垃圾收集器,其目的是获取最短垃圾回收停顿时间,它采用的是多线程的标记—清除,但是它需要更多的内存来完成这个动作;多应用于:
- 与用户交互较多的场景
- 希望系统的停顿时间最短,注重服务的响应速度
- 给用户带来较好的体验
- 常见的WEB、B/S系统的服务器应用上
# CMS的运行过程
1.初始标记
只是标记一下GC Roots能直接关联的对象,速度很快但仍然需要暂停所有的工作线程;
目前主流的JVM都是准确式GC,可以直接得知哪些内存存放着对象引用,所以系统执行GC Roots停顿时,并不需要全部、逐个查找全局性和执行上下文的引用位置;
.
在HotSpot中,是是用一组称为OopMap 的数据结构来达到这个目的的:
- 在类加载时计算对象内什么偏移量上是哪个数据
- 在JIT编译的时候,也会记录栈和寄存器中哪些位置是引用
这样在GC扫描的时候就可以直接得到信息
2.并发标记
进行GC Roots跟踪的过程,从刚才产生的集合中标记存活的对象,并发执行不需要暂停工作线程;
但是并不能保证标记出所有的存活对象;
3.重新标记
为了修正并发标记期间因为用户程序继续运行而导致标记变动的那一部分对象的标记记录;需要“Stop The World”且停顿时间比初始标记时间长但远比并发标记的时间短;
4.并发清除
回收所有的垃圾对象;
–gc
清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。由于耗时最长的并发标记和并发清除阶段垃圾收集线程是和用户线程并行工作的,所以总体来看CMS的内存回收和用户线程是一起并发执行的
CMS收集器显著的三个缺点:
概述 | 详解 | 解决方案 |
---|---|---|
对CPU资源敏感 | 并发收集虽然不会暂停用户线程,但是因为占用了一部分的CPU资源,所以造成系统变慢,吞吐量降低 | 增量式并发收集器,类似于抢占式来模拟多任务机制的思想,让收集线程和用户线程交替运行,减少收集线程的运行时间 |
无法处理浮动垃圾,可能出现Concurrent Mode Failure失败 | (1)在并发清除时,用户线程产生新的垃圾叫做浮动垃圾,这使得在并发清除之前需要预留一定的内存空间,不能像其他收集器一样等到老年代快要填满的时候在进行收集 (2)如果CMS预留空间无法满足程序要求,就会出现一次Concurrent Mode Failure失败;这使得CMS启动临时预案:使用Serial Old来处理老年代垃圾,这将会导致另一次的Full GC | |
产生大量的内存碎片 | 由于CMS采用的是标记-清除算法,所以在垃圾回收之后不能进行压缩操作,造成内存碎片问题;若在内存碎片不足以值称创建一个新的的大型对象的时候,会触发Full GC对新生代+老年代进行垃圾回收 | (1)-XX:+UseCMSCompactAtFullCollection使得CMS出现问题时不进行Full GC而是开启内存碎片的整合过程(2)-XX:+CMSFullGCsBeforeCompaction 设置n次的不压缩碎片的Full GC之后来一次压缩整理 |
# 7.G1收集器
G1 (Garbage-First)垃圾收集器,是在JDK1.7之后才出的一款商用的垃圾回收器;
# G1的收集步骤
步骤 | 解释 |
---|---|
初始标记(Initial Marking) | 仅标记一下GC Roots能关联的对象,且修改TAMS(Next Top At Mark Start)让下一个阶段并行运行时用于程序能在可用的region中创建对象 ,需要Stop The World 但是速度很快 |
并发标记(Concurrent Marking) | 在进行GC Roots 遍历的时候,标记出刚才存活的对象 |
最终标记(Final Marking) | 为了修改在并发标记期间因为用户的并行运行而导致标记变动的那一部分的记录,需要Stop The World且停顿的时间比初始标记稍长,但是比并发标记短 |
筛选回收(Live Data Counting and Evacuation) | 采取复制算法,从一个或多个region复制存活对象到堆的另一个region,并且在此过程中压缩和释放内存,可采用并发进行,降低停顿时间,并增加吞吐量 |
它的特点有以下几种:
# 特点1:并行与并发
G1收集器能充分利用CPU、多核环境下的硬件优势,使用多个CPU核心来缩短Stop The World的停顿时间。部分其他的垃圾回收器需要在GC的时候使工作线程停下来,而G1和CMS一样都可以通过并发回收的方式让收集线程和工作线程一起并行执行;
# 特点2:分代收集,收集范围包括新生代和老年代
虽然G1不需要与其他的垃圾回收器配合,自己可以管理整个堆的GC,但是他还是保留了分区的概念。他能够采用不同的方式去处理新生的对象和存活时间很长的对象;
# 特点3:内存布局
G1将内存分为若干个等大小的区域(region),Eden/Survivor/Old
分别是region的逻辑集合,物理上存在并不一定连续;
G1在GC策略上已经没有了Full GC,他区分的使Fully Young GC和Mixed GC;
GC策略 | 解释 |
---|---|
Fully Young GC | 年轻代的垃圾回收 |
Mixed GC | 混合了年轻代和老年代的GC |
但是在这里存在一个跨代引用的问题:
当垃圾回收时,先从GC Roots开始,这会先经过新生代,在经过老年代,这没有什么问题。但是老年代引用新生代的对象会影响新生代的 Young GC ,这种跨代需要处理。为了避免在回收年轻代的时候扫描整个老年代,需要记录老年代对年轻带的引用,Young GC的时候只需要扫描这个记录。
在CMS和G1中都用到了一个叫做Card Table(卡表)
的数据结构,但是用法不太一样。
收集器 | 用法 |
---|---|
CMS | JVM将内存分为一个固定大小的card,然后用card table来维护每个card的状态,一个字节对应一个card。当一个card的状态发生变化,就将这个card的card table的状态置为dirty,young gc只需要扫描dirty card即可 |
G1 | 在card的基础上引入Rset(remember set)。每个区域(region)都会维护一个rset,记录着从本region到其他region的card。比如A对象在region A,B对象在region B ,且B.f=A ,则region A中的rset需要记录region B 的card所在地址 这样的好处是可以对region进行单独回收,这要求rset不但要维护从年轻代到年老代的引用,也要维护年老代到年轻代的引用,对于跨代扫描只需要每次都扫描rset上的card |
# 特点4:实现多种垃圾收集算法
- 从整体上看,是基于标记-整理算法;
- 从局部看,是基于复制算法;
他们都不会产生内存碎片,有效的利用了系统资源;
# 特点5:可预测的GC停顿
G1除了追求低停顿外,还能建立可预测的停顿时间模型;其可以明确的指定M毫秒时间片内,垃圾消耗时间不超过N毫秒;
为什么G1可以实现预测停顿时间模型:
- 可以有计划的避免在Java堆中进行全区域的垃圾回收;
- G1根据各个region获取其收集价值的大小,在后台维护一个优先列表;
- 每次根据允许的收集时间,优先回收价值最大的region(名称Garbage -First的由来
- 01
- python使用生成器读取大文件-500g09-24
- 02
- Windows环境下 Docker Desktop 安装 Nginx04-10
- 03
- 使用nginx部署多个前端项目(三种方式)04-10