一、什么是垃圾回收器
根据垃圾回收器这个名字我们可以知道,其主要作用是用来回收内存中已被判定无用的垃圾对象。但是垃圾回收器在扫描过程中,寻找并标记的其实是还在存活的对象。当查找完全部存活对象后将未标记的对象进行统一的回收。
对于一个垃圾回收器,它其实主要需要完成三件事情
分配内存:垃圾回收算法的设计往往决定了内存模型和内存分配的方式
确保存活对象不会受到垃圾回收的影响
回收垃圾对象
二、垃圾回收器的分类
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算法,不会产生内存碎片。