JVM垃圾回收算法以及垃圾回收器看这一篇就够了!

MinorGC,MajorGC, FullGC

  • MinorGC 清理整合新生代: eden 的清理,幸存者区的清理

    • Minor GC触发条件:当Eden区满时,触发Minor GC。
  • MajorGC清理整个OldGen的内存空间

  • FullGC 清理整个堆空间—包括年轻代和永久代。

    • Full GC触发条件:

      (1)调用System.gc时,系统建议执行Full GC,但是不必然执行

      (2)老年代空间不足

      (3)方法区空间不足

      (4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

      (5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

对象是否为垃圾的判定

引用计数法

引用计数法名副其实,每当有一个地方引用了该对象实例那么计数器就+1(a = b,则b引用的对象实例的计数器+1)

每当有一个地方取消了对该对象实例的引用也就是引用失效那么计数器就-1

优点:任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。

缺点:循环引用问题,如父对象有一个对子对象的引用,子对象反过来引用父对象,这样,他们的引用计数永远不可能为0,它们也永远不会被回收。

可达性分析算法

程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。

固定可作为GC Roots对象包括一下几种:

  • 在虚拟机栈(栈帧中的本地变量表)中所引用的对象,比如各个线程被调用的方法堆栈中使用到的参数,局部变量,临时变量等
  • 在方法区中类静态属性引用的对象,比如Java类的引用类型静态变量
  • 在方法区中常量引用的对象,比如字符串常量池里的引用
  • 在本地方法栈中JNI(Java Native Interface)引用的对象
  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些异常对象(NullPointException,OutOfMemoryErroe)等,还有系统类加载器
  • 所有被同步锁(synchronized)持有的对象

参考自《深入理解Java虚拟机》-周志明 70页

垃圾收集算法

标记清除算法

它是最早出现也是最基础的垃圾收集算法,正如他的名字一样,标记清除算法分为两个阶段,标记和清除:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来。标记过程就是对象是否为垃圾的判定过程。

🙂优点:简单

😢缺点:1.执行效率不稳定,如果说堆空间中大量对象需要被回收,那么标记过程和清除过程都会因对象数量的增长而变得效率低下。

​ 2.会产生内存碎片,在Java堆中需要放一些大对象时,因无法找到足弓的连续内存而不得不提前触发一次垃圾回收。

复制算法(主要采用在新生代)

标记-复制算法也被简称为复制算法。

✔解决问题:解决标记清楚算法面对大量可回收对象时执行效率低的问题。

幸存者区使用了复制算法,将还在存活的对象的空间分为两块,不被回收的放入幸存者2区,执行完毕后,直接把幸存者1区的对象全部清除。

🙂优点:1.在多数对象是可回收的情况下,该算法需要复制的就是占少数的存活对象,而且每次都是针对整个半区进行的内存回收,而且 每次只需要移动堆栈指针按顺序分配即可。因此复制算法只适用于对象存活数量较少的情况,这也正好符合了新生代的场景。

​ 2. 不会产生碎片化。

😢缺点:1.在多数对象是存活的情况下,该算法需要复制的就是多数的存活对象,它会产生对于内存的大量开销。

​ 2.显而易见,内存的使用效率下降了一倍。

标记整理算法(主要采用在老年代)

标记整理也被叫做标记压缩算法。

❌老年代中的对象大部分都是存活时间比较长的,采用复制算法则会导致复制次数过多而导致效率低下。

❌标记-清除算法也可以应用在老年代中,但是该算法效率低下,而且会产生碎片化。

标记整理算法就是标记可回收的对象,然后将未被标记的存活的对象移动到内存空间的一端,然后将存活的对象边界外的对象全部回收。

🙂优点:解决的标记-清除算法的产生碎片化的缺点,解决的复制算法中内存减半的缺点

😢缺点:移动对象的同时,如果对象被其他对象引用,则需要调整引用的地址,且需要全程暂停用户应用程序即STW

小结

Mark-SweepMark-CompactCopying
速度中等最慢最快
空间开销少(但会堆积碎片)少(不堆积碎片)通常需要活对象的两倍大小(不堆积碎片)
移动对象

分代收集

难道没有最优的算法么? 没有的,没有最好的算法,只有最合适的算法。

不同的对象的生命周期是不一样的,因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。

新生代:对象生命周期较短,利用复制算法

老年代:标记-压缩,标记-清除算法

​ Mark(标记)阶段的开销与存活对象的数量成正比

​ Sweep(清除)阶段的开销与所管理区域的大小成正相关

​ Compact(压缩)阶段的开销与存活对象的数据成正比

扩展:增量收集算法

每次收集线程只收集一小片区域的内存空间,接着切换到应用程序线程,以此反复,直到收集完毕。**但是线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降**

增量收集算法的基础仍是传统的标记-清除和复制算法,它通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成标记,清理或复制工作

分区收集

将整个堆区划分为连续的不同小区间,每一个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小空间。

垃圾收集器

评估GC性能的指标

吞吐量:运行用户代码的时间/总运行时间(程序运行时间+内存回收时间)

暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间

内存占用:Java堆区所占的内存大小。

主要关注的时吞吐量暂停时间两个指标。它们是矛盾的,只能单方面的好。

Serial收集器:串行回收

这个收集器是一个单线程的收集器,但它的单线程的意义不仅仅说明它只会使用一个CPU或一条收集线程区完成垃圾收集工作,它进行垃圾回收时,必须暂停其他所有的工作线程(STW),直到垃圾收集结束。

  • Serial 作为Client(客户端)模式下的默认新生代垃圾收集器
    • Serial收集器采用复制算法,串行回收和“STW”机制的方式执行内存回收
  • Seria Old 作为Client(客户端)模式下的默认老年代垃圾收集器
    • Serial Old是标记-压缩算法,串行回收和”STW“机制的方式执行内存回收

ParNew收集器:并行回收

ParNew收集器除了采用并行回收的方式执行内存回收外,几乎与Serial回收器没有任何区别。

  • 在新生代仍然使用复制算法和STW机制

ParNew是很多JVM运行在Server(服务端)模式下新生代的默认垃圾回收器

  • 老年代可用Serial Old或CMS

Parallel回收器:并行回收,吞吐量优先

Parallel收器和ParNew收集器一样也采用了并行回收,复制算法和STW机制。

但是不同的是它的目标则是达到一个可控制吞吐量自适应调节策略也是Parallel和ParNew一个重要区别

⭐CMS(Concurrent(并发)-Mark(标记)-Sweep(清除))收集器:低延迟

这款收集器是虚拟机中第一款真正意义上的并发收集器,它**第一次实现了让垃圾收集线程与用户线程同时工作**,CMS关注点就是尽可能的缩短垃圾收集时用户线程的停顿时间,所以它具有低延迟的特性。目前很大一部分Java应用集中在互联网站或者B/S(浏览器与服务器)系统的服务端都用CMS回收器。

⭐⭐CMS四个主要阶段

  1. 初始标记:会触发STW,标记GC Roots能直接关联到的对象。
  2. 并发标记:从GC Roots开始遍历整个对象图,虽然耗时较长,但是不影响用户线程的执行。
  3. 重新标记:修正并发标记期间因为用户程序的执行而导致标记变动的那一部分对象的标记记录。它的主要作用就是重新确定并发标记阶段的标记对象是否是垃圾。
  4. 并发清理:清理删除掉标记阶段判断的已经死亡的对象,释放内存空间

🙂优点:并发收集,低延迟

😢缺点:1.会产生内存碎片:在并发清除后,如果用户线程想要分配大对象,而大对象找不到连续的内存分配空间,就会触发一次Full GC。

​ 2.CMS收集器对CPU的资源非常敏感:垃圾回收的线程会导致用户线程减少,应用程序变慢,吞吐量降低。

​ ⭐3.CMS收集器无法处理浮动垃圾:并发标记阶段如果出现新的垃圾,那么这些垃圾就无法被回收,这就是浮动垃圾。因为重 新标记只是去确定并发标记阶段的对象是否是垃圾。

⭐既然Mark Sweep会产生内存碎片,那么为什么不把算法换成Mark Compact呢?

第三阶段如果使用标记压缩算法,对象的地址就会被改变,用户线程的执行就会崩溃。

小结

Serial收集器:最小化的使用内存和并行开销,串行回收

ParNew收集器:最大化应用程序的吞吐量,并行回收

ParNew收集器:除了采用并行回收的方式执行内存回收外,几乎与Serial回收器没有任何区别。

CMS收集器:最小化GC的中断和停顿时间,并行回收

⭐G1收集器:区域化分代式

官方给G1设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才担起“全功能收集器”的重任与希望。

G1是一个并行收集器,它把堆内存分割为很多不相关的区域(物理上不连续),使用不同的Region来表示Eden,幸存者0区,幸存者1区,老年代等。G1 GC有计划地避免在整个JAVA堆中进行全区域地垃圾收集,G1 跟踪各个Region里面地垃圾堆积地价值大小(回收所获得地空间大小以及回收所需时间地经验值),==在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。==这种方式的侧重点在于回收垃圾最大量的区间(Region),所以我们给G1一个名字:垃圾优先(Garbage Fist)

Region

将整个Java堆区划分成约2048个大小相同的独立Region块,每个Region块大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间,只能是2的n次幂

一个Region有可能属于Eden,Survivor或者Old内存区域,但是一个Region只能有一个角色。如果一个对象超过1.5个Region就会放到H区域中,如果一个H区装不下,那么G1会寻找连续的H区来存储

为什么设置Region

对于堆中的大对象,默认会直接被分配到老年代,但是如果它是一个生命周期较短的大对象,那么对垃圾收集器就会造成负面影响,因为老年代的垃圾回收频率不高,这个大对象会占用老年代的内存空间。

垃圾回收过程

  1. 年轻代GC:当年轻代的Eden区用尽是开始年轻代回收过程,这个阶段是一个并行的独占式收集器(STW机制),和堆空间回收新生代过程一样。采用复制算法,一定会用到R set(记忆集),因为年轻代回收频率很高,不能每次都去扫描老年代对新生代的引用。
  1. 老年代并发标记过程:堆内存达到阈值45%时,开始老年代并发标记过程。G1老年代回收器一次只需要扫描/回收一部分老年代的Region就可以。

  2. 混合回收Mixed GC:部分老年代region和年轻代一起回收的

Remebered Set

一般来说, gc 过程是这样的:首先枚举根节点。根节点有可能在新生代中,也有可能在老年代中。

这里由于我们只想收集新生代(换句话说,不想收集老年代),所以没有必要对位于老年代的 GC Roots 做全面的可达性分析来判断是否引用了新生代。但问题是,确实可能存在位于老年代的某个 GC Root,它引用了新生代的某个对象,那怎么办呢?同样的在其它收集器中也存在同样的问题。

仍然是拿空间换时间的办法。事实上,对于位于不同年代对象之间的引用关系,虚拟机会在程序运行过程中给记录下来。“老年代对象引用新生代对象”这种关系,会在引用关系发生时,在新生代边上专门开辟一块空间记录下来,这就是 RememberedSet 。所以“新生代的 GC Roots ” + “ RememberedSet 存储的内容”,才是新生代收集时真正的 GC Roots 。

每次引用类型数据进行写操作时,还会产生一个写屏障暂时中断写操作,然后检查将要写入的引用指向的对象是否和该引用类型数据在不同的Region(其他收集器:检查老年代对象受否引用的新生代对象),如果不同的话通过Card Table把相关的引用信息记录到引用指向对象的所在Region对应的Remeber Set中,当进行垃圾收集的时候,在Gc根节点的枚举范围加入Remebered Set,就可以保证不进行全局的扫描,也不会有遗漏。然后就可以以此为据,在新生代上做可达性分析,进行垃圾回收。

我们知道, G1 收集器使用的是化整为零的思想,把一块大的内存划分成很多个域( Region )。但问题是,难免有一个 Region 中的对象引用另一个 Region 中对象的情况。为了达到可以以 Region 为单位进行垃圾回收的目的, G1 收集器也使用了 Remembered Set 这种技术,在各个 Region 上记录自家的对象被外面对象引用的情况。

七种经典垃圾回收器总结

垃圾收集器分类作用位置使用算法特点适用场景
Serial串行运行新生代复制算法响应速度优先单CPU的client模式
Serial Old串行运行老年代标记压缩响应速度优先单CPU的client模式
ParNew并行运行新生代复制算法响应速度优先多CPU环境下Server模式下和CMS配合使用
Parallel并行运行新生代复制算法吞吐量优先适用于后台运算和不需要太多的交互场景
Parallel Old并行运行老年代标记压缩吞吐量优先适用于后台运算和不需要太多的交互场景
CMS并发运行老年代标记清除响应速度优先B/S业务
G1并发,并行运行新生代和老年代复制算法和标记压缩响应速度优先面向服务端应用

并发,并行和串行

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值