JVM之垃圾回收器

2024-05-22  
0条评论   128浏览

一、什么是垃圾回收器

根据垃圾回收器这个名字我们可以知道,其主要作用是用来回收内存中已被判定无用的垃圾对象。但是垃圾回收器在扫描过程中,寻找并标记的其实是还在存活的对象。当查找完全部存活对象后将未标记的对象进行统一的回收。

对于一个垃圾回收器,它其实主要需要完成三件事情

分配内存:垃圾回收算法的设计往往决定了内存模型和内存分配的方式

确保存活对象不会受到垃圾回收的影响

回收垃圾对象

二、垃圾回收器的分类

JDK1到JDK13的发展历程中,一共出现了10种垃圾回收器:

如上图中垃圾回收器之间存在连线,则表示他们可以搭配使用。 每一种垃圾回收器都有各自的特点,没有最好的垃圾收集器,只有最适合的。我们可以根据他们之间不同的特点选择需要的垃圾回收器,甚至可以搭配使用,提升效率。

针对不同的垃圾回收器的特点 有一个概念比较容易搞混

并行,串行,并发之间的区别

首先通过一个很简单的比喻区分并行和串行之间的区别: 有一天小明要去银行注销掉一张许久不用的银行卡,因为这天是周六,银行办理业务的人非常多,大家需要通过排号系统按照不同的需求进行排队来办理自己的业务。

那么什么是串行呢??

此时由于已经到中午12点,银行的业务人员大部分出去吃午饭了,此刻银行只开放了一个值班窗口,所有的人都要排队等候这一个值班窗口叫号。上一个办理业务的人如果没有办理完业务,下一个人需要一直等待。

那什么叫并行呢??

过了午饭时间,银行因为工作人员的回归开放了其他的业务窗口,多个窗口可以在同一时间进行叫号并处理业务,虽然号码排在你前面的那个人的业务还没有处理完,但是随着其他窗口的空闲,你也能够被叫到号,这就是并行。

那么最后一个问题就是什么叫做并发呢

当排到你的时候,你突然想起要办理一张公积金联名卡,于是询问业务员是否可以在办理注销的同时申请一张新卡。业务员回答你由于他业务不熟练,无法做到办理注销的同时还能处理你申请新卡的业务,需要等办理注销结束后才能支持新卡的申请。这就是不支持并发,单次只能处理一种任务,无法做到通过任务排序分时执行不同的任务,达到并发。 而如果此时是个熟练地业务员呢,他可以再你注销的同时帮你完成申请新卡的业务,有序的完成两个你交代的任务,同时处理,这就是支持并发。 例子举得可能有些偏差,但是这三者之间的概念大致如此。

1. Serial(串行收集器)

特点:

1.Serial只会使用一个CPU或者一条GC线程进行垃圾回收,并且在垃圾回收过程中暂停其他工作线程。是clinet模式的默认回收器。

2.采用copy算法实现

3.单核CPU效率最高,适合Clinet模式(应用内存小,不会创建太多对象,因此垃圾回收时间较短,即使停掉业务线程也不会感到明显的停顿)

4.只有一条GC线程,避免了线程切换的开销

2.ParNew

ParNew就是Serial的多线程版本

特性

1.ParNew由多条GC线程并行的进行垃圾清理工作,清理过程中需要停掉所有的业务线程,但由于是多线程运作,其效率要高于serial

2.由于支持多线程运作,因此更适用于CPU较多的服务器环境。由于多线程之间的切换回造成额外的开销,因此在单CPU环境中表现并不如Serial。其默认开启线程数与CPU核数相同

3.采用COPY算法实现

3.Parallel Scavenge

1.并行多线程回收器,常用于新生代,追求CPU吞吐量的优化,能在较短的时间内完成指定的任务,因此适合不需要太多交互的后台运算。

吞吐量是指用户线程运行时间占CPU总时间的比例,其计算公式为:

吞吐量=运行用户代码时间/(运行用户代码时间+GC的时间)

吞吐量越高表示GC时间占比越低,用户体验越好

2.采用复制算法实现

(1)降低停顿时间的两种方式

1.在多CPU环境中使用多条GC线程,从而垃圾回收的时间减小,从而使用户线程STW的时间减小。 2.实现GC线程与用户线程并发运行,其所谓的并发指的其实是用户线程与GC线程交替运行,从而达到每次的停顿时间减小,用户的停顿感降低,单线程之间的不断切换也意味着需要额外的开销,从而垃圾回收和用户线程的总时间将会延长。

(2)Parallel Scavenge提供的参数

-XX:GCTimeRadio 直接设置吞吐量大小,GC时间占总时间比率.相当于是吞吐量的倒数 -XX:MaxGCPauseMillis 设置最大GC停顿时间

parallel 会根据这个值来决定新生代的大小,如果这个值越小,新生代就会越小,从而收集器就能以较短的时间来进行一次回收。

-XX:+UseAdaptiveSizePolicy 通过命令就能开启GC 自适应的调节策略(区别于ParNew).我们只要设置最大堆(-Xmx)和MaxGCPauseMillis或GCTimeRadio,收集器会自动调整新生代的大小、Eden和Survior的比例、对象进入老年代的年龄,以最大程度上接近我们设置的MaxGCPauseMillis或GCTimeRadio

4.Serial Old

Serial Old 收集器是Serial的老年代版本,他们都是单线程收集器,也就是垃圾收集时只启动一条GC线程,因此都适合客户端的应用,他们之间的主要区别其实就是Serial old常被用于老年代,其实现算法为mark-compact

5.Parallel Old

Parallel Old 收集器是PS收集器的老年代版本,一般他们搭配使用,追求CPU的吞吐量。 他们在垃圾手机时都是由多条GC线程并行执行,并暂停一切用户线程,使用mark-compact算法。

6.CMS (concurrent mark sweep)

CMS作用于老年代,是一种以获取最短停顿时间为目标的收集器。给予标记-清除算法实现。整个过程分为四步

1.初始标记

停止一切用户线程,因使用一条初始标记线程对所有与GC Roots关联的对象进行标记。 2.并发标记

使用多条并发标记线程并行执行,并与用户线程并发执行。此过程进行可达性分析,标记出所有废弃的对象,速度很慢。 3.重新标记

使用多条线程并行执行,将刚才并发过程中新出现的废弃对象标出来。 4.并发清除

使用一条并发清除线程,和业务线程并发执行,清除无用对象,这个过程非常耗时。

CMS的特点

吞吐量低

由于CMS在垃圾收集过程使用用户线程和GC线程并发执行,从而线程之间切换会有额外的开销,因此CPU吞吐量就不如在业务线程全部通知的情况下高。 无法处理浮动垃圾 由于垃圾清理过程中,可能会产生浮动垃圾,当浮动垃圾过多时,可能会导致频繁的GC。 浮动垃圾:在并发标记的情况下,GC线程标记对象未存活对象后用户线程不再会用此对象,该对象此时没有任何的引用,这种对象被称为浮动垃圾

使用mark-sweep算法实现没会产生内存碎片 可以通过开启-XX:+UseCMSCompactAtFullCollection参数是每次fgc后进行一次内存压缩。还可以配置-XX:CMSFullGCsBeforeCompaction告诉CMS经历多少次FGC后进行一次内存整理

7.G1(GarBage-First)

G1可以看做是CMS的加强版。G1的算法流程和CMS相似,其所不同的有:

1.G1通过mark-compact算法实现,这意味着每次GC结束获得的都是连续的空间。

2.G1虽然依旧采用了分代的处理方式,但是他的内存模型有了巨大的变化。他的内存基本结构被分成了一块又一块的Region。G1负责维护一个Region的列表。每次需要进行GC的时候,他首先会评估每个Region的回收价值,然后回收掉价值最大的Region,从而获得最大的GC回收效率。

(1)G1的回收过程

初始标记:

标记与GC Root对象直接关联的对象,停止所有业务线程(STW),只启动一条初始标记线程,这个过程很快。

并发标记:

进行全面的可达性分析,开启一条并发标记线程记性有用对象标记,此线程与用户线程并行执行,这个过程较长。

最终标记

标记出并发标记过程中业务线程产生的浮动垃圾,停止所有业务线程(STW),执行多条最终标记线程。 筛选回收 回收无用对象,此时也是STW状态,使用多条筛选回收线程执行。

(2)G1的特性

CMS因为并发引起的问题G1同样也存在,但是G1可以避免因为Mark-Sweep算法引起的内存不连续问题。 G1由于可以在用户指定时间范围内进行垃圾回收,所以具备软实时的特性。 如上图,G1拥有一个特殊的区域叫做Humongous。 什么事Humongous:如果一个对象占用的空间达到或者超过了分区大小的50%以上,G1收集器会认为这是一个巨型对象。这种巨型对象会被默认分配到老年代,但是如果这个巨型对象只会短期存在,就会影响G1的垃圾回收效率,为了解决这个问题,G1专门划分了Humongous区域来存放巨型对象,当一个Humongous区域存放不下一个巨型对象时,G1会寻找连续的H分区来存储,有时为了寻找连续的H区,不得不启动FULL GC。

8.小结

上述七种垃圾回收器中分别应用于不同的场景。其中Serial,parNew,parallel scavenger(PS)三种回收器作用于新生代,通过copy算法实现。其中Serial适合单核的Client端,ParNew适合多核服务器。而PS和ParNew类似,只不过更加注重吞吐量。另外,PS不能与CMS一起搭配使用。 Serial Old,ParNew Old和CMS常用来针对老年代的回收。Serial Old 采用的都是Mark-Compact算法实现,而CMS使用的是Mark-Sweep算法,因此会产生内存碎片。 G1是一个横跨新生代和老年代的处理器,他区别于上述回收器的分代模型的是采用了全新的分区模型。通过region的划分,每个区域都可以充当新生代或者老年代。G1采用的Mark-compact算法,不会产生内存碎片。