GC垃圾回收

Java知识点总结:想看的可以从这里进入

4、GC垃圾回收

垃圾回收机制简称GC,主要用于Java堆的管理。在JVM中程序计数器、虚拟机栈、本地方法栈生命周期随跟随线程,栈帧的进栈和入栈能实现自动清理。而 jdk8后元空间为本地内存也不受GC控制,所以垃圾回收主要是在堆中。

程序在运行的时候,随着访问的增多,会有大量的对象不断创建,有些对象的生命周期很短,所以不可避免就会出现一些垃圾数据,这些数据对程序来说已经无法访问,为了避免影响程序的性能,java虚拟机在程序运行的过程中不断地进行自动的垃圾回收(GC)。

在Java中GC的清理是不定时的,不能指定强制清除某个对象,即使我们能明确判断某个区域无用,也无法确定清除,我们能做的就是可以提醒虚拟机这块区域已经无用,可以进行清除可,但是虚拟机具体什么时候清除我们无法得知。

我们常说的finalize()方法就是在每次执行GC操作之前时会调用的方法,它是在Object类中定义的,子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

GC的种类:

  1. Minor GC(新生代GC):发生在新生代的GC,触发的十分频繁,回收速度也非常快。

    一般当eden区满时或申请的对象大于eden剩余空间都会触发。每个收集器都支持单独的新生代回收。

  2. Major GC(老年代GC):发生在老年代的GC,通常执行Major GC会连着Minor GC一起执行。Major GC的速度要比Minor GC慢的多。当晋升到老年代对象大于老年代剩余空间会触发

    当老年代满后触发,只有CMS会有单独的老年代收集行为,其他均无。

  3. Full GC是清理整个堆空间,包括年轻代和老年代

  4. MixedGC:混合收集。G1收集器特有,收起新生代和部分的老年代

4.1、判断垃圾对象

  • 引用计数器

    给对象设置一个计数器,被引用时+1,失效时-1,当为0时表示没有被引用,可以回收,但通常因为循环依赖的问题,这种方法一般不采用

  • 可达性分析法

    Java的垃圾回收处理机制采用了可达性分析法,它以一个GC Root根对象为起点,向下查询,将每一个查询的路径作为一个引用链,当对象不在任何一条引用链上时,则判定为垃圾对象。

    如:root→4→5→6→7 这样一个引用链。当5和6之间的引用断开,意味着,6和7无法追溯到根,这时候java会判定不再使用,这时候java会对6和7做个标记。

    但是判定为垃圾对象后,仅仅是做一个标记,JVM还会给这些对象一次机会,当GC后,会先判断被标记对象是否执行了 finalize 方法,如没有执行,会执行 finalize方法,在方法内会再次进行可达性的判断,如果可达则不回收,如果确定不可达了,就会对其进行回收。所以一般至少要经过两次标记才会回收

在这里插入图片描述

4.2、回收算法

  1. 标记清除算法:最基础的垃圾回收算法,分为标记和清除,先将可回收的对象标记为可回收,然后再清除被标记对象。从后图中可以看到,这种算法在清除后会使内存碎片化。

    在这里插入图片描述

在这里插入图片描述

  1. 复制算法:将内存划分为两块,每次只使用其中1块,当内存满时,将存活对象复制到另一块,然后清除垃圾对象。对象过多时效率慢,且内存压缩到原来的一半(参考新生代)
    在这里插入图片描述

在这里插入图片描述

  1. 标记整理算法:先标记可回收的对象,然后将存活的对象移动到内存一端后,再回收边界外的其余对象。可有效的解决内存碎片化的问题。移动对象需要更新引用,每次GC都需要移动对象。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  1. 分代收集算法:JVM把堆分成新生代、老年代即是分代收集算法,根据对象的存活周期划分不同的区域存放,可根据不同的区域使用不同的回收方法

  2. 分区收集算法:G1回收器使用的算法,将堆等分成若干的区,每个区之间可互相独立,任何一个区都可作为伊甸区、存活区、老年区。可灵活的控制回收的区域,减少GC产生的停顿,对大内存提升尤其明显

4.3、垃圾处理器

4.3.1、性能指标

GC垃圾处理器的性能指标:

  1. 吞吐量:运行用户代码时间 /(程序的运行时间+垃圾收集时间)
  2. 垃圾收集开销:吞吐量的补数,垃圾收集所用时间 / 总运行时间的比例
  3. 暂停时间:执行垃圾收集时,程序的工作线程被停止的时间
  4. 收集频率:相对于应用程序的执行,垃圾操作发生的频率。(并非越低越好)
  5. 内存占用:Java堆所占内存大小
  6. 快速:一个对象从诞生到回收所经历的时间

这些指标中吞吐量、暂停时间、内存占用比较重要。随着硬件不断的提升,内存越来越大,所以内存占用比较能接受,而硬件的提升也能提高一定的吞吐量,所以目前暂停时间越来越重要。而垃圾处理器的选择就是根据不同的场景偏重某个方面。(JVM的调优一般就是针对吞吐量和暂停时间,两者存在一定的冒顿,所以要在这两者之间做一个均衡。如:G1垃圾收集器的原则是在最大吞吐量优先的情况下,降低停顿时间。)

4.3.2、处理器
  • 新生代处理器

    1. Serial垃圾收集器(单线程、复制算法):JDK1.3前新生代唯一选择,是一个单线程的收集器,它不但只会使用一个CPU或一条线程去完成垃圾收集工作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。

      Serial垃圾收集器工作时需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java虚拟机运行在Client模式下默认的新生代垃圾收集器。

    2. ParNew垃圾收集器(Serial+多线程):Serial的多线程版本,执行时也会停止其他工作线程,但默认开启和CPU数目相同的线程数,可以通过-XX:ParallelGCThreads参数来限制垃圾收集器的线程数。是JDK7前首选的新生代收集器,可以和CMS配合使用。

    3. Parallel Scavenge收集器(多线程复制算法、高效):Parallel Scavenge 收集器是一个新生代垃圾收集器,使用复制算法,也是一个多线程的垃圾收集器,但它是并行的,它重点关注的是程序达到一个可控制的吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),高吞吐量可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务。自适应调节策略也是ParallelScavenge收集器与ParNew 收集器的一个重要区别。

  • 老年代处理器

    1. Serial Old收集器(单线程标记整理算法):是Serial垃圾收集器的老年代版本,具体特性和Serial相似。

    2. Parallel Old收集器(多线程标记整理算法):Parallel Scavenge收集器的老年代版本,JDK1.6开始提供。多线程的标记整理算法,

    3. CMS收集器(多线程标记清除算法):Concurrent mark sweep(CMS)收集器是一种老年代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间,使用多线程的标记-清除算法。多用于B/S架构的服务端。第一次实现了让垃圾收集线程与用户线程可同时工作。它只能和ParNew或者时Serial配合使用(JDK9被弃用,14被移除)

      1. 初始标记:标记GCRoots能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。(STW时间很短)
      2. 并发标记:从GC Roots直接关联对象出发遍历整个对象引用结构,和用户线程一起工作,不需要暂停工作线程。(无STW)
      3. 重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。长于初始标记,短于并发标记。(STW时间短)
      4. 并发清除:清除GCRoots不可达对象,和用户线程一起工作,不需要暂停工作线程。(无STW)

      由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看CMS收集器的内存回收和用户线程是一起并发地执行。

      由于CMS使用的是标记-清除算法,所以回收对象之后就会产生内存碎片化,对于分配一些较大内存对象内存空间的时候就很尴尬,且内存的不连续也不能使用指针碰撞技术,只能使用空闲列表来进行内存的分配

  • G1收集器:JDK9后取代CMS,Garbage first垃圾收集器是目前垃圾收集器理论发展的最前沿成果,基于标记整理和复制算法,不会产生内存碎片,可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。

  • ZGC(The Z Garbage Collector)是标记-整理算法的并发垃圾回收器,:JDK11后推出,

4.4、G1垃圾回收器

在JDK9以后默认的垃圾回收处理器为G1(Garbage One),取代了以前的CMS垃圾回收器。

  1. 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。

    -XX:MaxGCPauseMillis=time 参数来设置一个停顿时间。默认是200ms

  2. G1收集器把堆内存划分为大小固定的几个独立区域Region,每个区都可以作为Eden、Survivor或old。然后跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域,避免进行全局的GC。
    区域划分和优先级区域回收机制,确保G1收集器可以在有限时间获得最高的垃圾收集效率。在低内存时G1个CMS的性能相差不大,但是当内存越来越大时,G1的性能就越优于CMS。

    1. -XX:G1HeapRegionSize=size:就是设置它这个区域的大小(1 / 2 / 4 / 8 / 16 / 32) (Jvm默认分为2048个区,即默认大小为: 堆内存/2048)
    2. -XX:G1NewSizePercent 设置新生代初始占比。默认新生代对堆内存的占比是5%,最多新生代的占比不会超过 60%

在这里插入图片描述

  1. 使用标记整理+复制算法:Region 区域之间是复制算法,但是在Region在经过GC后会被整理到一起。

G1的垃圾回收的三个过程:

在这里插入图片描述

  1. Young Collection/Minor GC:新生代的垃圾回收:并行的、独占式的(STW)的垃圾回收,可能会发生对象的代晋升,将会把对象放入Survivor或者是老年代。

    当划分的Eden区慢慢被占满时,触发Young Collection 新生代的垃圾回收,停止程序线程,创建回收集,通过复制算法把Eden区的存活对象复制到Survivor区,年龄+1,把超过存活年龄阈值的对象复制到老年代,其余幸存对象和Eden区存活对象复制到新的幸存区。(清空后的Region会在空闲列表中等待新的角色分配)

在这里插入图片描述
在这里插入图片描述

  1. Young Collection + CM:并发标记阶段
    当堆空间的内存占用达到阈值(-XX:InitiatingHeapOccupancyPercent,默认45%)就开始老年代的并发标记过程。

    • 初始标记阶段:标记出从GC Root开始直接可达的对象,让下一阶段用户线程并发运行时能正确的在可用的Region中分配新对象,耗时很短,和MinorGC同步完成。会发生STW(但暂停时间很短),会触发一次Young GC
    • 根区域的扫描:G1扫描Survivor区直接可达的老年代区域对象,并标记被引用的对象。这一个过程必须在Young GC之前完成。
    • 并发标记(CM):在整个堆中进行并发标记(与程序线程并发执行),此过程可能会被Young GC打断,在并发标记阶段中,若发现某些region中所有对象都是垃圾,那这个region就会被立即回收,同时并发标记过程中,会计算每个region存活对象的比例(G1垃圾回收的时候根据回收的价值高低来优先回收价值较高的region)
    • 最终标记(Final Marking):由于并发标记阶段是收集器的标记线程和程序线程并发执行的,需要进行再次标记,修正上一次的标记结果,会出现STW(暂停时间较短)。
    • 筛选回收:计算各个Regin的存活对象和GC回收比例,对各个Regin的回收价值和成本进行排序,根据用户所期待的GC停顿时间指定回收计划,回收一部分Region,需暂停用户线程,多条收集线程并发处理,会发生STW。
  2. Mixed Collection:对Eden、Survivor或old进行全面的垃圾回收,是一个混合垃圾回收器。

    首先会进行新生代的垃圾回收。
    老年代的回收耗费时间较长,所以为了低于最大暂停时间,会优先选择其中垃圾最多的区域,把那些通过标记后还存活的对象复制到新的老年区,然后将其余垃圾对象进行回收(但是如果回收所有老年区的时间低于最大暂停时间,就会回收所有的老年区)

G1的几个运行标记步骤:

  1. 初始标记(Initial Marking):这个阶段是STW(Stop the World)的,所有应用线程会被暂停,标记出从GC Root开始直接可达的对象,让下一阶段用户线程并发运行时能正确的在可用的Region中分配新对象,耗时很短,和MinorGC同步完成。
  2. 并发标记:从GC Roots开始对堆中对象进行可达性分析,递归扫描整个堆,找出存活对象,耗时较长,但可与用户线程并发运行,扫描完成后需要重新处理SATB记录的在并发时有变动的对象。
  3. 最终标记(Final Marking):对用户线程做短暂暂停处理,标记那些在并发标记阶段发生变化的对象,将被回收
  4. 筛选回收:首先对各个Regin的回收价值和成本进行排序,根据用户所期待的GC停顿时间指定回收计划,回收一部分
    Region,需暂停用户线程,多条收集线程并发处理

4.5、对象申请堆空间

  1. 当一个对象被创建时,会在堆中申请内存使用。
  2. 首先在Eden区中判断空间,如果此空间大小不超过Eden的 1/2 ,且空间充足,则直接保存,如果空间不够,会在Eden内执行GC操作清除无用的空间后,继续判断空间是否充足,如果充足则分配空间。如果大小超过 1/2 则会直接存放在老年区。
  3. 如果eden的空间在GC后还不足,则会在Survivor中判断空间是否充足,如果充足则将eden中的存活对象转入Survivor区,然后在eden为新对象分配空间
  4. 如果Survivor空间不足则进行GC处理,然后将Eden存活对象转入Survivor,在Eden为新对象分配空间。
  5. 如果GC后Survivor还是没有空间,则去老年代判断空间大小,将部分存活时间长的对象转入老年代。
  6. 如果老年代的空间不足,则进行GC处理,如果此后空间还是不足则出现OOM异常。

对象创建时实现存放在Eden区,当Eden区空间存满后,JVM触发一次新生代GC,将存活对象复制到Survivor的TO区,将年龄设置为1,并回收垃圾对象,然后将From区的存活对象年龄加1,将年龄小于15的复制到To区,将年龄大于15的晋升到老年代,并回收垃圾对象,最后将From和To区的角色进行互换,到老年代中的内存满后触发一次全堆的GC

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辰 羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值