JVM垃圾收集算法 & 垃圾收集器

垃圾收集器简介

最近上海疫情,在家办公加上五一假期,就看了好多之前买的技术书。又重新拿起“深入理解java虚拟机”看了看,之前看的时候都是似懂非懂的,在看了前几本技术书之后再看,觉得顺眼了很多,最近刚看完垃圾收集器相关的内容,就随手记录下,日后用到了就回来看看~

垃圾收集算法

标记-清除算法

标记清除算法是最基础的算法。它的收集过程主要分为两步,就是标记 & 清除,首先标记出需要被回收的对象,在标记完成后统一回收所有被标记的对象。之所以说它是最基础的垃圾收集算法,是因为后续的其他算法都是在它的基础上优化出来的。
它的不足主要有两点:一个是效率问题。它的标记和清除两个操作的效率都不高;另一个就是内存空间问题。因为它只清除标记的对象,清除之后会产生大量的碎片内容,如果一个大对象进来需要一块连续的内存空间,这个时候如果满不足了就不得不触发GC。

复制算法

为了解决效率问题,出现了复制算法。它是将可用的内存空间划分为两个大小相等的区域,每次新的对象都分配在其中一个上面,当不够存放的时候,这一块触发GC,然后把存活下来的对象挪到另外一块上,再把剩下的内存全部回收掉。这种好处是每次都只会回收一般的内存,效率相对较高,但是缺点也很明显,它使得可用的内存变成了原来的一半。
这种在虚拟机中的使用一半都是把内存划分为一块较大的Eden和两块较小的Survivor。每次只使用Eden和一个Survivor,当回收时,将Eden和Survivor上存活的对象复制到另一个Survivor上,然后清理掉剩下的空间。因为很多对象的存活时间都很短,所以Eden和Survivor不必要按照1:1来,默认的虚拟机是8:1。但是复制算法当对象存活率较高的时候,效率就比较低了~

标记-整理算法

复制手机算法在队形存活率较高时就要进行较多的复制操作,效率就会变低。所以这种复制算法不适用老年代。
根据老年代的特点,提出了“标记-整理”算法,标记过程和之前的“标记-清除”一样,但是后续清理步骤不是直接把标记对象清除,而是让所有存活的对象都向内存的一端移动,然后直接清除掉边界以外的内存。

分代收集算法

这种并没有在算法上提出什么新的意见。只是说应该把对象分为不同类型存储,一般划分为新生代和老年代。新生代就采用复制算法,老年代就采用标记-整理算法。

垃圾收集器

Serial收集器

Serial收集器是最基本、发展历史最悠久的收集器。在JDK1.3之前是虚拟机新生代收集器的唯一选择。这个收集器是单线程的,它只会使用一个SPU一个线程去执行垃圾回收,并且在进行垃圾回收的过程中,需要暂停其他所有的工作线程。所以Serial收集器不适用于client模式以外的虚拟机,不然它的工作效率低并且停止服务的时间长。client模式下没有多余的线程交互,执行也简单。

ParNew收集器

ParNew收集器其实是Serial收集器的多线程版本,所以它是许多运行在server模式下的虚拟机中首选的新生代收集器,其中一个很大的原因是,它可以搭配(CMS)配合工作。CMS后面会介绍。当ParNew工作的时候,也需要暂停其他工作线程。

Parallel Scavenge收集器

Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,也是支持多线程的,但是它和ParNew收集器最大的区别是,它可以达到一个可控制的吞吐量。吞吐量= 运行用户代码时间/(运行时间+垃圾回收时间)。虚拟机停顿时间短可以提升用户体验,但是吞吐量高就代码执行用户代码的时间多,可以高效的利用CPU,尽快完成程序的运算任务,适合没有太多用户交互的任务。
Parallel Scavenge收集器提供了两个参数用于控制吞吐量。分别是控制最大垃圾回收停顿时间-XX:MaxGCPauseMillis 以及直接设置吞吐量大小的-XXGCTimeRatio参数。不过这两个参数不能为了追求极致的吞吐率乱设置,所以 Parallel Scavenge收集器还提供了一个参数-XX:+UseAdaptiveSizePolicy(自适应动态调整参数)。一般会默认开启这个开关。

Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本,采用标记-整理算法。它主要用两大用途,一种是在JDK1.5以及之前的版本,搭配Parallel Scavenge使用;另一种就是作为CMS收集器的后备预案。

Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。不过比较尴尬的是,这个收集器是JDK1.6之后才提供的,在此之前,新生代的Parallel Scavenge收集器一直处于比较尴尬的状态。因为如果新生代选择了Parallel Scavenge收集器,老年代除了选择Serial Old收集器别无它选。所以Parallel Old收集器也不会提高多少效率,知道Parallel Old收集器出来之后,“吞吐量优先”收集器终于有了比较名副其实的应用组合,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge收集器和Parallel Old收集器。

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收时间为目标的收集器。目前很大一部分Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
从名字上(Mark Sweep)就可以看出来,CMS收集器是基于“标记-清理”算法实现的,它的运作过程相对于前面几种收集器来说更加复杂一些,整个过程分为4个步骤,包括:
初始标记 – Stop The World(暂停其他工作线程) – 标记GC Roots能直接关联到的对象
并发标记 – GC Root的Tracing过程
重新标记 – Stop The World(暂停其他工作线程)-- 修正并发标记期间因用户程序继续运气而导致标记产生变动的那一部分对象的标记记录(比初始标记长,远比并发标记时间短)
并发清除

CMS是一款优秀的收集器,但是它有三个明显的缺点:
一是CMS收集器对CPU资源非常敏感。CMS默认启动的垃圾回收线程是 (CPU数量+3)/4,也就是说当CPU数量在4个以上时,并发垃圾回收线程不少于25%的CPU资源,并且锁着CPU数量的增加而下降。但是当CPU数量小于2个时,CMS收集器对程序的影响就变得很大。
二是CMS收集器无法处理浮动垃圾。由于CMS并发清理阶段用户线程还在运行着,只好等下一次GC再处理这部分垃圾,这就是浮动垃圾。也就说明在出发GC的时候,并不是等内存占满之后才清理,而要留出一部分内存来给这部分运行中的用户线程使用。
三是CMS收集器是“标记-清除”算法,这样会产生内存碎片,当大对象来的时候就不得不出发FullGC。

G1收集器

G1(Garbage-First)收集器是当静收集器技术发展的最前沿的成果之一,稳定版发布于JDK7u4。G1是一块面向服务端应用的垃圾收集器,开发团队任务未来可以用它替换掉CMS收集器,于其他收集器相比,G1主要有以下特点:
并行与并发:充分利用多CPU、多核环境下的硬件优势,缩短stop the world的时间。
分代收集:G1收集器不需要搭配其他收集器来实现分代收集的目的,它可以独立管理整个GC堆,分代进行垃圾回收。
空间整合:G1采用的是“标记-整理”算法,意味着它不会产生内存碎片,分配大对象不会因为碎片空间不够而产生FullGC。
可以测停顿:G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾回收的时间不得超过N毫秒。

G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器是,java的堆内存布局就与其他收集器有很大差别,它将整个java堆划分为多个大小相等的独立区域(Region),虽然还保留新生代老年代的概念,但新生代和老年代不再是物理隔离的来,它们都是一部分独立的Region集合。

G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的REgion,这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

那么G1到底是怎么做到以Region为单位进行垃圾回收的呢?
在G1收集器中,Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆栈扫描的。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的region之中,如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set中。当进行内存回收时,在GC根节点的美剧范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。

如果不计算维护Remembered Set的操作,G1收集器的云国大致可划分为以下几个步骤:
初始标记:Stop The World(暂停其他工作线程) – 标记GC Roots能直接关联到的对象 && 修改TAMS(next Top at Mark Start)的值,让下一阶段用户线程并发运行时,能在正确可用的Region中创建对象。需要停顿,耗时很短。
并发标记:从GC Root开始对堆中对象进行可达性分析,找出存活对象,这阶段耗时长,但是可以和用户程序并发执行。
最终标记:修正并发标记期间因用户程序继续运气而导致标记产生变动的那一部分对象的标记记录,数据最终记录在Remembered Set中。可以和用户程序并发执行。
筛选回收:Stop The World(暂停其他工作线程)。先对各个Region的回收价值和成本进行排序,再根据用户期望的GC停顿时间来制定回收计划。

至此,垃圾回收的基础知识就整理完了。如果有不正确的地方,希望大家斧正~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值