【JVM】垃圾收集

一、垃圾收集算法

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

算法分为两个阶段:首先标记出所需要回收的对象,在标记完成后统一回收所有被标记的对象

1.1 不足
  1. 效率问题,标记和清除两个过程效率都不高;
  2. 空间问题,标记清除之后会产生大量的连续的内存碎片,空间碎片太多会导致以后在程序运行中需要分配较大对象时,无法找到足够的连续内存而不得提前触发另一次垃圾收集动作

标记清除

2. 复制算法(Copying)

为了解决效率问题,一种被称为复制的收集算法出现了,它将可用内存按容量划分为大小相等的两份,每次只是用其中的一块,当这一块内存使用完了,就将还存活的这对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉
在这里插入图片描述
复制算法

3. 标记整理算法

复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低;更关键的是,如果不想浪费50%的空间,就需要额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端,所以老年代一般不会使用此方法

标记-整理算法于标记清除算法的标记过程相同,但后续步骤不是直接清除,而是让所有的对象都向一端移动,然后直接清理掉端边界以外的内存;示意图图小:
标记整理

4. 分代收集算法

一般是把Java堆 分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代 中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付 出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间 对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。


二、收集算法的落地实现-收集器

垃圾收集器组合

1. Serial收集器

Serial [ˈsɪriəl] 连续的

Serial收集器是基本、发展历史悠久的收集器,曾经(在JDK1.3.1之前)是虚拟机新生代收集的唯一选择。 它是一种单线程收集器,不仅仅意味着它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是其 在进行垃圾收集的时候需要暂停其他线程。

优点:简单高效,拥有很高的单线程收集效率

缺点:收集过程需要暂停所有线程

算法:复制算法

适用范围:新生代 应用:Client模式下的默认新生代收集器
在这里插入图片描述

2. ParNew收集器

Serial收集器的多线程版本

优点:在多CPU时,比Serial效率高。

缺点:收集过程暂停所有应用程序线程,单CPU时比Serial效率差

算法:复制算法 适用范围:新生代

应用:运行在Server模式下的虚拟机中首选的新生代收集器

并行(Parallel):指多条垃圾收集器线程并行工作,但此时用户线程任然处于等待状态
并发 (Concurrent):用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集器程序运行在另一个CPU上

3. Parallel Scanvenge

Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器,看上去 和ParNew一样,但是Parallel Scanvenge更关注 系统的吞吐量 。

吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)

  -XX:MaxGCPauseMills  #控制最大垃圾收集停顿时间
  -XX:GCRatio  #直接设置吞吐量的大小
4. Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本,也是一个单线程收集器,不同的是采用"标记-整理算法",运行过程 和Serial收集器一样。

在这里插入图片描述

5. Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和"标记-整理算法"进行垃圾回收。

关注点:吞吐量优先

在这里插入图片描述

6. CMS收集器

CMS(Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器,采用的是标记-清除算法

执行步骤步骤:

  1. 初始标记(CMS initial mark) 标记GC Roots能关联到的对象 Stop The World —速度和很快

  2. 并发标记(CMS concurrent mark) 进行GC Roots Tracing

  3. 重新标记(CMS remark) 修改并发标记因用户程序变动的内容 Stop The World

  4. 并发清除(CMS concurrent weep)

    由于整个过程中,并发标记和并发清除,收集器线程可以与用户线程一起工作,所以总体上来说,CMS收集 器的内存回收过程是与用户线程一起并发地执行的。

优点: 并发收集、低停顿

缺点:产生大量的空间碎片,并发阶段会降低吞吐量;无法处理浮动垃圾
在这里插入图片描述

浮动垃圾:用于并发清理阶段用户程序还在运行着,伴随程序的运行自然就还会产生新的垃圾,这一部分垃圾出现在标记之后,CMS无法在当次收集中处理他们,只能等待下一次GC是处理掉,这一部分垃圾被称为“浮动垃圾"

因为浮动垃圾的存在,就需要预留多余的空间给线程使用,CMS收集器不能像其他收集器那样等到老年代几乎完全被填满才进行垃圾收集;
当没有足够的空间时,就会出现“Concurrent Mode Failure”失败,虚拟机将启动后被预案,临时启用Serial Old收集器来 重新进行老年代的垃圾收集

  -XX:CMSInitiatingOccupancyFraction   #设置触发垃圾回收百分比
7. G1收集器

G1是一款面向服务端应用的垃圾收集器;与其他收集器相比具备一下特点

并发与并行:

G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者 CPU核心)来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的 GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行

分代收集:

与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其 他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已 经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。

空间整合:

与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实 现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这 两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种 特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一 次GC。

可预测的停顿:

这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关 注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一 个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实 时Java(RTSJ)的垃圾收集器的特征了。

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

使用G1收集器时,Java堆的内存布局与就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的 独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们 都是一部分Region(不需要连续)的集合。

运行过程

初始标记(Initial Marking):标记以下GC Root

并发标记(Concurrent Marking)

最终标记(Final Marking)

筛选回收(Live Data Counting and Evacuation)
在这里插入图片描述

三、垃圾收集器的分类

  1. 串行收集器

    Serial 和Serial Old,只能一个垃圾回收线程执行,用户线程暂停,适用于内存比较小的嵌入式设备

  2. 并行收集器

    吞吐量优先,Parallel Scanvenge、Parallel Old,多线程垃圾收集器并行工作,但此时用户线程仍然处于等待状态;适用于科学计算、后台处理等交互场景

  3. 并发收集器
    CMS、G1 收集器;用户线程和垃圾收集线程同时执行(但不一定是并行,可能是交互执行),垃圾收集线程在执行的时候不会停顿用户线程的运行,适用于对时间有要求的场景,如:web

四、垃圾收集的时机

GC是由JVM自动完成的,根据JVM系统系统环境而定,所以时机是不确定的;当然,我们可以手动进行垃圾回收,比如调用System.gc()方法通知JVM进行几次垃圾回收,但是具体什么时候运行也无法控制,也就是说System.gc()只是通知要回收,什么时候回收,由JVM决定

一般一下几种情况会发生垃圾回收

  1. 当Eden区或者S区不够用了
  2. 当老年代空间不够用了
  3. 方法区空间不够用了
  4. System.gc();

五、堆内存的垃圾回收

垃圾回收的主要内存区域在堆内存中,先来看一下堆内存的内存结构:
堆内部结构划分

1. Survivor区详解

接着上面的GC来说,From区中对象一开始只有Eden区和From区中有对象,To区是空的

此时,进行一次GC操作,From区中对象的年龄就会加1,我们知道Eden区中所有存活的对象会被复制到To区,From区中对象有两个去处——如果对象的年龄达到之前设置好的年龄阈值,此处的对象会被移动到Old区中,没有达到阈值的对象会被复制到To;这时候From和To交换角色,之前的From变成了To,之前的To变成了From,也就是说无论如何都要保证名称To的Survivor区域是空的;Minor Gc会一直重复重复这个过程,直到To区被填满,然后会将所有对象复制到老年代中

2.Old区

存储年龄比较大的对象或相对超过了某个阈值的对象;在Old区也会有GC的操作,Old区的GC通常被称为Major GC,每次GC之后还存活的对象年龄也会加1,如果年龄超过了某个阈值,就会被回收

我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。 有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时 候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上 闯闯了。 于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年 (每次GC加一岁),然后被回收。

3. 内存回收策略
  1. 对象优先在Eden分配

  2. 大对象直接进入老年代

    # 当对象内存大于设置值时,直接进入老年代
    -XX:PretenureSizeThreshold
    
  3. 长期存活的对象将进入老年代
    如果对象在Eden区中经过第一次Minor GC后任然存活,并且被Survior容纳的话,将被移动到Survior空间,并且对象年龄设置为1,对象在Survior区中每熬过一次Monior GC,年龄增加1岁,当它的年龄增加到一定程度,就将会被晋升到老年代

    # 设置对象晋升到老年代的年龄
    -XX:MaxTenuringThreshold  
    
  4. 动态对象年龄判断
    如果Survivor空间中相同年龄所有对象的大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代;

  5. 空间分配担保
    在发生Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有的对象总空间,如果这个条件成立,那么Minor GC 可以确保是安全的;如果不成立,则虚拟机会查看HeadlePromotionFailure设置值是否允许担保失败;如果允许,那么会继续检查老年代最大最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure 设置不允许冒险,那么这时要改为进行一次Full GC

4. 回收过程图示

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值