Java知识梳理——GC/JVM中的垃圾回收机制/垃圾回收器

JVM如何确定垃圾?

1.引用计数法

算法实现:

给对象添加一个引用计数器
每当有一个地方引用该对象时,计数器的值就加1
每当有一个地方引用失效时,计数器的值就减1
任何时刻计数器的值为0的对象就是不可能再被使用的


但是引用计数法有一个很大的缺点:无法解决对象之间相互循环引用的问题

2.可达性分析算法

基本思想:

有一系列名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径被称为“引用链”
如果一个对象到GC Roots没有任何引用链相连接时,说明这个对象是不可用的
如果一个对象到GC Roots有引用链相连接时,说明这个对象是可用的
也就是说,给定一个集合的引用作为根节点出发,通过引用关系遍历对象图,能够遍历到的对象就被判定为存活,不能够被遍历到的对象说明对象死亡。

在这里插入图片描述

GC Roots就是一组必须活跃的引用

可以作为GC Roots的对象:

虚拟机栈中引用的对象(栈帧中的本地变量)
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中引用的对象

 

JVM的垃圾回收算法

1.标记清除算法(Mark-Sweep)

最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。如图

 

从图中我们就可以发现,该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。

2.复制算法(copying)

为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小 的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用 的内存清掉,如图:

3.标记整理算法(Mark-Compact)

结合了以上两个算法,为了避免缺陷而提出。标记阶段和 Mark-Sweep 算法相同,标记后不是清 理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。如图:

4.分代收集算法

分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃 圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。

我们知道Java 堆从 GC 的角度可以细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代。

 

新生代


是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发

MinorGC 进行垃圾回收。新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区。

Eden 区

Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老 年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行 一次垃圾回收。

ServivorFrom

上一次 GC 的幸存者,作为这一次 GC 的被扫描者。

ServivorTo
保留了一次 MinorGC 过程中的幸存者。

MinorGC 的过程(复制->清空->互换)

MinorGC 采用复制算法

1:eden、servivorFrom 复制到 ServivorTo,年龄+1

首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServivorTo 区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServivorTo 不 够位置了就放到老年区);

2:清空 eden、servivorFrom
然后,清空 Eden 和 ServivorFrom 中的对象;

3:ServicorTo 和 ServicorFrom 互换
最后,ServivorTo 和 ServivorFrom 互换,原 ServivorTo 成为下一次 GC 时的 ServivorFrom区。

老年代

老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行 了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足 够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。

MajorGC 采用标记整理算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没 有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减 少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的 时候,就会抛出 OOM(Out of Memory)异常。

 

JVM 7种垃圾收集器

如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。下图展示了7种不同分代的收集器,如果两个收集器之间存在连线,就说明他们可以搭配使用。并没有最好的收集器这一说,我们需要选择的是对具体应用最合适的收集器。

1、Serial收集器(用于新生代)

单线程,在进行垃圾收集时必须暂停其他所有的工作线程("Stop the World")。虚拟机运行在Client模式下的默认新生代收集器。简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程效率。

2、ParNew收集器(新生代)

ParNew收集器其实是Serial收集器的多线程版本,它是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为除了Serial收集器外,目前只有它能与CMS收集器配合工作。

3、Parallel Scavenge收集器(“吞吐量优先”收集器)(新生代)

        使用复制算法,并行多线程,这些特点与ParNew一样,它的独特之处是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目的则是达到一个可控制的吞吐量(Throughput),即CPU用于运行用户代码的时间与CPU总消耗时间的比值,吞吐量=运行用户代码时间 /(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,吞吐量就是99%。

       停顿时间越短对于需要与用户交互的程序来说越好,良好的响应速度能提升用户的体验;

       高吞吐量可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适合在后台运算而不太需要太多交互的任务。

参数设置:

-XX:MaxGCPauseMillis 控制最大垃圾收集停顿时间。(大于0的毫秒数)停顿时间缩短是以牺牲吞吐量和新生代空间换取的。(新生代调的小,吞吐量跟着小,垃圾收集时间就短,停顿就小)。

-XX:GCTimeRatio 直接设置吞吐量大小,0<x<100 的整数,允许的最大GC时间=1/(1+x)。

-XX:+UseAdaptiveSizePolicy  一个开关参数,开启GC自适应调节策略(GC Ergonomics),将内存管理的调优任务(新生代大小-Xmn、Eden与Survivor区的比例-XX:SurvivorRatio、晋升老年代对象年龄-XX: PretenureSizeThreshold 、等细节参数)交给虚拟机完成。这是Parallel Scavenge收集器与ParNew收集器的一个重要区别,另一个是吞吐量。

 

4、Serial Old收集器(老年代)

它是Serial收集器的老年代版本,单线程,使用“标记-整理”算法。主要意义是被Client模式下的虚拟机使用。如果在Server模式下,它还有两大用途:在JDK1.5及之前的版本中与Parallel Scavenge收集器搭配使用;作为CMS 收集器的后备预案,在并发收集发生Concurrent Mode Failure的时候使用。运行过程同Serial收集器。

5、Parallel Old收集器(老年代)

它是Parallel Scavenge收集器的老年代版本,多线程,使用“标记-整理”算法。在注重吞吐量及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old收集器。工作过程如下:

6、CMS收集器(Concurrent Mark Sweep)--同步标记清除

它是一种以获取最短回收停顿时间为目标的收集器。优点:并发收集,低停顿。基于“标记-清除”算法。目前很大一部分Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验,CMS收集器就非常符合这类应用的需求。运作过程较复杂,分为4个步骤:

初始标记(CMS initial mark):需要“Stop The World”,标记GC Roots能直接关联到的对象,速度快。
并发标记(CMS concurrent mark):进行GC Roots Tracing 过程
重新标记(CMS remark):需要“Stop The World”,修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。停顿时间:初始标记<重新标记<<并发标记
并发清除(CMS concurrent sweep):时间较长。


缺点:

对CPU资源非常敏感,面向并发设计的程序都会对CPU资源较敏感。CMS默认的回收线程数: (CPU数量+3)/4

无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。并发清理阶段用户程序运行产生的垃圾过了标记阶段所以无法在本次收集中清理掉,称为浮动垃圾。CMS收集器默认在老年代使用了68%的空间后被激活。若老年代增长的不是很快,可以适当调高参数-XX:CMSInitiatingOccupancyFraction  提高触发百分比,但调得太高会容易导致“Concurrent Mode Failure”失败。

基于“标记-清除”算法会产生大量空间碎片。提供开关参数-XX:+UseCMSCompactAtFullCollection 用于在“ 享受”完Full GC服务之后进行碎片整理过程,内存整理的过程是无法并发的。但是停顿时间会变长。

-XX:CMSFullGCsBeforeCompation 设置在执行多少次不压缩的Full GC后,跟来来一次带压缩的。

7、G1收集器(Garbage First)https://blog.csdn.net/coderlius/article/details/79272773

它是当前收集器技术发展的最前沿成果。与CMS相比有两个显著改进:

 

1.基于“标记-整理”算法实现收集器
2.非常精确地控制停顿


G1收集器可以在几乎不牺牲吞吐量的前提下完成低停顿的内存回收,这是由于它能够极力避免全区域的垃圾收集,之前的收集器进行收集的范围都是整个新生代或老年代,而G1将整个Java堆(包括新生代、老年代)划分为多个大小固定的独立区域(Region),并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域(这就是Garbage First名称的由来)。区域划分、有优先级的区域回收,保证了G1收集器在有限的时间内可以获得最高的收集效率。


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值