Java垃圾收集算法与垃圾回收器全解

目录

垃圾回收判定算法

判定算法一:引用计数算法

判定算法二:可达性分析算法

可作为GC Roots的对象

Java中的引用类型

方法区的回收

垃圾回收算法

回收算法一:标记——清除算法

回收算法二:复制算法

传统复制算法

变种复制算法

回收算法三:标记——整理算法

垃圾回收器

回收器一:Serial收集器

回收器二:ParNew收集器

回收器三:Parallel Scavenge收集器

回收器四:Serial Old收集器

回收器五:Parallel Old收集器

 回收器六:CMS收集器

CMS分为四个步骤:

CMS的缺点:

回收器七:G1收集器

G1的特点:

G1执行步骤:


垃圾回收判定算法

在Java堆中存放了几乎所有的对象实例,垃圾回收器在回收前需要检查哪些对象是死的,哪些对象是活的,然后回收那些死的对象。

判定算法一:引用计数算法

引用计数算法实现简单,判定效率高,大多数时候都是个不错的算法,他是这样实现的:

给对象添加一个引用计数器,每当有一个地方引用他时,计数器就+1,当引用失效时,计数器-1,那么计数器为0的对象就是没有任何地方引用的对象,可以回收。

但是引用计数算法有个缺点,碰到“相互循环引用”时就不行了。

例如:

class Test{
    public Object instance=null;

    public static void testGC(){
        Test A = new Test();
        Test B = new Test();
        A.instance = B;
        B.instance =A;    
    }
}

A的成员变量引用B,B的成员变量引用A......

GG


判定算法二:可达性分析算法

很多主流的语言例如Java、C#等都是使用可达性分析算法来判断对象死活的,这个算法思想是这样的:

以GC Roots作为起点向下搜索,将搜索到的节点连起来,如果一个对象无法连接到GC Roots,则证明此对象不可用。

即使这个对象与其他对象有关联,例如上图object5、6、7之间是联系的,但是他们不能到达GC Roots,不好意思,你们死了。

 

可作为GC Roots的对象

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

Java中的引用类型

上面谈了这么多,一直提到引用(reference),我们来了解一下Java中的几种不同的引用类型。

  • 强引用,如果一个对象具有强引用,例如 Test A = new Test();只要强引用还存在,就永远不会被回收。
  • 软引用,还有用但不是必须的对象,软引用关联的对象只有在即将内存溢出时才会把这些对象列入回收范围进行回收。
  • 弱引用,如果一个对象具有弱引用,那么垃圾回收器在扫描到该对象时,无论内存充足与否,都会回收该对象。
  • 虚引用,虚引用并不决定对象生命周期,如果一个对象只具有虚引用,那么它和没有任何引用一样,任何时候都可能被回收。为对象设置虚引用的唯一目的就是为了在对象被回收时可以收到一个系统通知。

方法区的回收

方法区是有垃圾回收的,虽然他收集的远比新生代要少,但是还是存在的。

例如无用的类,废弃的常量,字符串常量等。

而且在大量使用反射、动态代理、CGLib的情况下,方法区的回收是非常有必要的,以防出现内存溢出。

JDK8中方法区(永久代)已被移除,取而代之的是元空间(metaSpace),元空间不再使用JVM虚拟机,而使用本地内存,字符串常量也转移到堆中存放。


垃圾回收算法

回收算法一:标记——清除算法

根据判定算法判定出需要回收的对象并标记,标记完成后统一回收。

两个缺点:

  • 效率较低
  • 内存碎片严重,有可能引起大对象来临时无合适空间分配,引起下一次垃圾回收。

回收算法二:复制算法

传统复制算法

将内存五五开分成两部分,只使用其中一半,当内存快满时触发垃圾回收,将这一半内存中还存活的对象复制到另一块内存上去并整齐排好~不产生内存碎片,然后再清理掉之前的那一半内存。

 

变种复制算法

现在的大多数虚拟机都使用的是变种的复制算法来回收新生代,因为新生代大部分对象都是“朝生夕死”,回收率很高,所以不用五五开划分,而使用8:1:1的比例来划分。

  • Eden区  8           (新对象都会创建在这里,如果是超大对象则直接升级为老年代,不在这里存放,此处为新生代)
  • From Survivor区 1
  • To Survivor区 1  (如果这里没有足够的空间来存放存活的对象时,则对象直接进入老年代,称为分配担保机制

当垃圾回收时,会将Eden区和From Survivor区还存活的对象复制到To Survivor区中,然后清理到Eden和From Survivor,最后把To Survivor当做新的From Survivor,From Survivor作为新的To Survivor。


回收算法三:标记——整理算法

由于复制算法需要牺牲一定内存空间,而老年代中的大多数对象回收率不高,甚至出现100%存活的极端情况,所以老年代一般不使用复制算法,而使用标记——整理算法。

标记——整理算法和标记——清除算法差不多,多了一个将其整理好的功能,以免出现内存碎片影响大对象分配。


垃圾回收器

回收器一:Serial收集器

Serial收集器是最基本、发展时间最长的收集器,这是一个单线程收集器,也就是说在serial执行的时候需要暂停其他所有线程(stop the world)。

图中出现的Safepoint名为安全点,意思是:因为程序不能随时可以停下来进行GC,所以设置一个安全点,等所有的线程都跑到这个安全点的时候,再进行GC。


回收器二:ParNew收集器

ParNew其实就是Serial的多线程版本,使用多个线程来完成回收工作,他的其他行为包括控制参数和Serial几乎是一模一样的。


回收器三:Parallel Scavenge收集器

Parallel Scavenge收集器是一个新生代收集器,也是使用复制算法,也是并行多线程收集器。

Parallel Scavenge收集器的特点是:它的关注点和其他收集器不同,其他收集器关注的是更少的STOP THE WORLD,尽量减少停顿时间,而Parallel Scavenge收集器关注的是吞吐量,吞吐量提的是运行真正的用户代码的时间与总时间的比值。可以理解为Parallel Scavenge收集器是为了减少垃圾收集所占运行总时间的比例。

越需要用户交互的程序越需要停顿时间短,来保证良好的反应速度,而高吞吐量则可以高效率的利用CPU,尽快完成程序运算任务,适合后台运算(用户交互少)。


回收器四:Serial Old收集器

Serial Old收集器是Serial的老年代版本,专门用来回收老年代,同样,它也是单线程收集器,使用的是标记——整理算法。


回收器五:Parallel Old收集器

由于Parallel Scavenge新生代收集器只能和Serial Old收集器配合工作,导致它的吞吐量优势无法发挥出来(Serial Old是单线程收集器,会拖累Parallel Scavenge),所以Parallel Old出现后,Parallel Scavenge+Parallel Old的组合可以完美的发挥高吞吐量的优点。


 回收器六:CMS收集器

CMS(concurrent mark sweep)的特点是停顿时间短,追求极致相应速度。

从名字就可以看出来,CMS基于标记——清楚算法,并且是多线程并发收集器。

CMS分为四个步骤:

  • 初始标记
  • 并发标记
  • 重新标记
  • 并发清除

初始标记:仅仅只标记一下GC Roots能关联到的对象,时间很短;

并发标记:与用户线程同时执行,标记对象,持续时间较长;

重新标记:为了修正并发期间因程序继续运行导致标记产生变动的那些对象的标记记录;

并发清除:与用户线程同时执行,清除对象,持续时间较长;

CMS的缺点:

  • 对CPU资源敏感,CMS对用户程序的影响随着CPU的减少而增加,甚至在只有两个CPU的时候需要占用接近50%的资源。
  • CMS无法处理浮动垃圾(清理阶段新出现的垃圾),这些垃圾只能在下一次GC再回收,也正是由于CMS在回收时程序还在运行并且创造垃圾,所以每次回收必须提前开始,默认情况下,CMS在老年代达到92%时就被激活,如果CMS运行期间预留的内存无法满足程序需要,就会报出Concurrent Mode Failure(失败),然后虚拟机启动后备预案,暂停所有线程,使用Serial Old来回收老年代。
  • CMS使用标记——清除,所以会产生内存碎片,内存碎片太多时就会导致无法分配大对象,引起Full GC,为了避免这样,CMS在顶不住压力要进行Full GC时会开启整理内存碎片。

回收器七:G1收集器

G1收集器可以说是现在最先进的收集器之一。

G1的特点:

  • G1能充分利用多CPU、多核环境的优势进行并发工作。
  • G1不再划分新生代老年代,而是将堆划分为多个相等的区域(Region),虽然还保留新生代老年代的概念,但是他们现在不是隔离的了。
  • G1根据各个Region回收的价值(回收所得的空间大小)以及历史回收时间而得到的“经验”来生成一个优先级队列,在允许的收集时间限制内,根据优先级每次回收优先级(价值)最高的。

G1执行步骤:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收


垃圾回收机制的一些知识点

  1. 大多数情况下,对象在新生代的Eden区分配,如果Eden内存不够,则触发一次Minor GC。
  2. 大对象直接分配进老年代,避免因为要给大对象让位置出来而在空间还有余时提前触发GC。可以通过参数设置阈值。
  3. 新生代中的对象通过计数器来计算“年龄”,每熬过一次Minor GC,计数器+1,当达到默认15岁时就进入老年代(阈值可设置)。
  4. 虚拟机存在动态对象年龄判定机制,意思是对象不一定需要到15岁才进老年代,如果相同年龄的对象占了整个Survivor空间的一半以上,那么这个年龄及以上的对象直接进入老年代。
  5. 分配担保机制:每次进行minor GC时,由于Survivor区的大小是有限的,有可能存活的对象大小超过了Survivor区的容纳大小,那么就要将超出的对象放入老年代。但是分配担保是有条件的,需要老年代剩余的连续空间大小大于新生代所有对象的总大小(为了避免极端情况,全部存活),这样的Minor GC是安全的;如果小于,则虚拟机检查老年代的最大可用连续空间是否大于历次晋升老年代对象的平均大小,如果大于,则可以冒险尝试Minor GC,如果小于,抱歉,Full GC吧。

个人学习总结,有不正确之处望指正。

参考资料:《深入理解JVM虚拟机》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值