jvm总结二《GC算法》

本文内容:何时发生GC? GC算法有哪些? 哪些对象被回收?

一、什么时候触发GC

(1)程序调用System.gc时可以触发

(2)系统自身来决定GC触发的时机(根据Eden区和From Space区的内存大小来决定。当内存大小不足时,则会启动GC线程并停止应用线程)

GC又分为 Young GC 和 Full GC (也称为 Major GC )
Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件:
调用System.gc时,系统建议执行Full GC,但是不必然执行
b.老年代空间不足
c.方法区空间不足
d.通过Young GC后进入老年代的平均大小大于老年代的可用内存
e.由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

二、GC算法有哪些

1.标记-清除算法

最基础的收集算法 —— 标记/清除算法
之所以说标记/清除算法是几种GC算法中最基础的算法,是因为后续的收集算法都是基于这种思路并对其不足进行改进而得到的。标记/清除算法的基本思想就跟它的名字一样,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

  • 标记阶段:标记的过程其实就是前面介绍的可达性分析算法的过程,遍历所有的GC Roots对象,对从GC Roots对象可达的对象都打上一个标识,一般是在对象的header中,将其记录为可达对象;
  • 清除阶段:清除的过程是对堆内存进行遍历,如果发现某个对象没有被标记为可达对象(通过读取对象header信息),则将其回收。
    在这里插入图片描述
优点
  • 实现简单
  • 与保守式GC算法兼容(保守式GC在后面介绍)
缺点
  • 效率问题。标记和清除两个阶段的效率都不高,因为这两个阶段都需要遍历内存中的对象,很多时候内存中的对象实例数量是非常庞大的,这无疑很耗费时间,而且GC时需要停止应用程序,这会导致非常差的用户体验。
  • 空间问题。标记清除之后会产生大量不连续的内存碎片(从上图可以看出),内存空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。

2.复制回收算法

为了解决效率问题,一种称为“复制”(Copying)的收集算法出现了,它将可用的内存按照容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况。只要移动堆指针,按照顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半。
在这里插入图片描述

优点
  • 优秀的吞吐量。
  • 可实现高速分配:复制算法不用使用空闲链表。这是因为分块是连续的内存空间,因此,调用这个分块的大小,只需要这个分块大小不小于所申请的大小,移动指针进行分配即可。
  • 不会发生碎片化。
  • 与缓存兼容。
缺点
  • 将内存缩小为原来的一半,浪费了一半的内存空间,代价太高;

  • 如果对象的存活率很高,极端一点的情况假设对象存活率为100%,那么我们需要将所有存活的对象复制一遍,耗费的时间代价也是不可忽视的。

基于以上复制算法的缺点,由于新生代中的对象几乎都是“朝生夕死”的(达到98%),现在的商业虚拟机都采用复制算法来回收新生代。由于新生代的对象存活率低,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的From Survivor空间、To Survivor空间,三者的比例为8:1:1。每次使用Eden和From Survivor区域,To Survivor作为保留空间。GC开始时,对象只会存在于Eden区和From Survivor区,To Survivor区是空的。GC进行时,Eden区中所有存活的对象都会被复制到To Survivor区,而在From Survivor区中,仍存活的对象会根据它们的年龄值决定去向,年龄值达到年龄阀值(默认为15,新生代中的对象每熬过一轮垃圾回收,年龄值就加1)的对象会被移到老年代中,没有达到阀值的对象会被复制到To Survivor区。接着清空Eden区和From Survivor区,新生代中存活的对象都在To Survivor区。接着, From Survivor区和To Survivor区会交换它们的角色,也就是新的To Survivor区就是上次GC清空的From Survivor区,新的From Survivor区就是上次GC的To Survivor区,总之,不管怎样都会保证To Survivor区在一轮GC后是空的。GC时当To Survivor区没有足够的空间存放上一次新生代收集下来的存活对象时,需要依赖老年代进行分配担保,将这些对象存放在老年代中。

3.标记-整理算法

复制算法在对象存活率较高时要进行较多的复制操作,效率会变得很低,更关键的是,如果不想浪费50%的内存空间,就需要有额外的内存空间进行分配担保,以应对内存中对象100%存活的极端情况,因此,在老年代中由于对象的存活率非常高,复制算法就不合适了。根据老年代的特点,高人们提出了另一种算法:标记/整理算法。从名字上看,这种算法与标记/清除算法很像,事实上,标记/整理算法的标记过程任然与标记/清除算法一样,但后续步骤不是直接对可回收对象进行回收,而是让所有存活的对象都向一端移动,然后直接清理掉端边线以外的内存。
在这里插入图片描述

优点
  • 弥补了标记/清除算法存在内存碎片的问题
  • 也消除了复制算法内存减半的高额代价
缺点
  • 标记/整理算法的缺点就是效率也不高,不仅要标记存活对象,还要整理所有存活对象的引用地址,在效率上不如复制算法。

4.分代回收算法

这个算法没有没有什么新的思想,只是根据对象的生命周期不同把内存分为几块。一般是把堆内存分为新生代、老年代。这样可以根据年代的不同采用不同的GC算法。
新生代:每次GC后会有大量的对象被回收,所以采用复制算法。
老年代:对象存活率高,没有额外空间对它进行分配担保,就必须使用”标记-清理”或者“标记-整理”算法来进行回收。
对象每经历一次yangGC,对象年龄就会+1,对象年龄为15的时候这个对象就会进入老年代

三、哪些对象被回收?

怎么判定对象为可回收对象呢?
引用计数算法

对象每被引用一次,引用计数器加1,引用失效计数器减1.效率高,但是无法解决循环引用问题:比如A->B,B->A。这个时候AB对象是永远不会回收的

可达性分析算法

基本思想是通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。
在这里插入图片描述
一个简单的图说明一下:
和GCRoot构成引用链的有obj1 obj2 obj3,而obj4和obj5他们两个之间虽然有引用链,但是却没有和GCRoot构成引用链,所以,obj4和obj5可能被回收掉

可作为GC Roots的对象包含以下几种:

  • 系统类加载器(bootstrap)加载的类。
  • JVM方法区中静态属性引用的对象。
  • JVM常量池中引用的对象。
  • JVM虚拟机栈中引用的对象。
  • JVM本地方法栈中引用的对象。
  • 活动着的线程

四、java中的引用

说到GCORoot了,这里就需要说说引用了。
java中把内存中对象的引用分成了四个等级,在不同程度下进行垃圾的回收工作。
按照回收优先级顺序分为:虚引用 -> 弱引用 -> 软引用 -> 强引用

  • 强引用
    开发中用的最多的就是强引用了。
    只要某个对象与强引用关联,那么JVM在内存不足的情况下,宁愿抛出outOfMemoryError错误,也不会回收此类对象。
    如果我们想要JVM回收此类被强引用关联的对象,我们可以显示地将obj 置为null,那么JVM就会在合适的时间回收此对象。
    在这里插入图片描述
    在这里插入图片描述
  • 软引用
    软引用是用来描述一些非必需但仍有用的对象。
    在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。
    软引用适合做缓存,在内存足够时,直接通过软引用取值,无需从真实来源中查询数据,可以显著地提升网站性能。当内存不足时,能让JVM进行内存回收,从而删除缓存,这时候只能从真实来源查询数据。比如网页缓存,图片缓存等。
    在这里插入图片描述
    在这里插入图片描述
    上图就是软引用,因为内存充足,所以GC之后obj没有被回收
  • 弱引用
    如果某个对象与弱引用关联,那么当JVM在进行垃圾回收时,无论内存是否充足,都会回收此类对象。
    java中使用WeakReference来表示弱引用。
    弱引用可以在回调函数在防止内存泄露。因为回调函数往往是匿名内部类,一个非静态的内部类会隐式地持有外部类的一个强引用,当JVM在回收外部类的时候,此时回调函数在某个线程里面被回调的时候,JVM就无法回收外部类,造成内存泄漏。

在这里插入图片描述在这里插入图片描述上图就是弱引用,发生GC后无论内存是否充足,都会回收

  • 虚引用
    虚引用,引用就像形同虚设一样,就像某个对象没有引用与之关联一样。若某个对象与虚引用关联,那么在任何时候都可能被JVM回收掉。虚引用不能单独使用,必须配合引用队列一起使用。
    java中使用PhantomReference来表示虚引用
    当垃圾回收器准备回收一个对象时,如果发现它与虚引用关联,就会在回收它之前,将这个虚引用加入到引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被回收,如果确实要被回收,就可以做一些回收之前的收尾工作。
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值