Java 杂谈JVM专题JVM · Java虚拟机原理 · JVM上语言·框架· 生态系统

JVM之垃圾收集算法

2018-12-19  本文已影响9人  LandHu

背景:看完《深入理解Java虚拟机》和相关博客,对JVM还是没有一个条理清晰的认识,遂提取了书中相关知识点和参考相关优秀博客并整理成JVM专题博文系列,帮助自己巩固并理清有关JVM的知识重点,也分享出来给有需要的童鞋,如有差错,欢迎拍砖!

标记-清除算法

最基础的算法,分标记和清除两个阶段:首先标记处所需要回收的对象,在标记完成后统一回收所有被标记的对象。

它有两点不足:
一个效率问题,标记和清除过程都效率不高;
一个是空间问题,标记清除之后会产生大量不连续的内存碎片(类似于我们电脑的磁盘碎片),空间碎片太多导致需要分配大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。


标记-清除算法.png

复制算法(标记-复制-清除算法)

为了解决效率问题,出现了“复制”算法,他将可用内存按容量划分为大小相等的两块,每次只需要使用其中一块。当一块内存用完了,将还存活的对象复制到另一块上面(移动堆顶指针,按顺序分配内存),然后再把刚刚用完的内存空间一次清理掉。这样就解决了内存碎片问题,但是代价就是可以用内容就缩小为原来的一半。

目前此种算法主要用于新生代回收(图中有标注)。
因为新生代的中98%的对象都是很快就需要被回收的对象,这一点大家在编程时可以体会到,所以并不需要1:1的比例来划分内存空间,在新生代中JVM是按照“8:1:1”的比例(图中有标注)来将整个新生代内存划分为一块较大的Eden区和两块较小的Survivor区(S0、S1)。

每次使用Eden区和其中一个Survivor区,当发生回收时将Eden区和Survivor区中还存活的对象一次性复制到另一块Survivor区上,最后清理掉Eden区和刚才使用过的Survivor区。理想情况下,每次新生代中的可用空间是整个新生代容量的90%(80%+10%),只会有10%的内存会被浪费。实际情况中,如果另外一个10%的Survivor区无法装下所有还存活的对象时,就会将这些对象直接放入老年代空间中(这块在后面的分代回收算法会说到,这里先了解下)。


复制算法.png

标记-整理算法(标记-清除-压缩算法)

复制算法在对象存活率较高时就会进行频繁的复制操作,效率将降低,而且如果不想浪费50%的内存空间的话,就还需要额外的空间进行分配担保,以应对存活对象超额的情况。显然老年代不能采用2)中的复制算法。
因此又有了标记-整理算法,标记过程同标记-清除算法,但是在后续步骤不是直接对对象进行清理,而是让所有存活的对象都向一侧移动,然后直接清理掉端边界以外的内存。


标记-整理算法

分代收集算法

当前商业虚拟机的GC都是采用分代收集算法,这种算法并没有什么新的思想,而是根据对象存活周期的不同将堆分为:新生代和老年代,方法区称为永久代(在新的版本中已经将永久代废弃,引入了元空间的概念,永久代使用的是JVM内存而元空间直接使用物理内存),这样就可以根据各个年代的特点,采用合适的收集算法了。

运行数据区.png

新生代中的对象“朝生夕死”,每次GC时都会有大量对象死去,少量存活,使用复制算法。新生代又分为Eden区和Survivor区(Survivor from、Survivor to),大小比例默认为8:1:1。

老年代中的对象因为对象存活率高、没有额外空间进行分配担保,就使用标记-清除或标记-整理算法。

回收过程

新产生的对象优先进去Eden区,当Eden区满了之后再使用Survivor from,当Survivor from 也满了之后就进行Minor GC(新生代GC),将Eden和Survivor from中存活的对象copy进入Survivor to,然后清空Eden和Survivor from,这个时候原来的Survivor from成了新的Survivor to,原来的Survivor to成了新的Survivor from。复制的时候,如果Survivor to 无法容纳全部存活的对象,则根据老年代的分配担保(类似于银行的贷款担保)将对象copy进去老年代,如果老年代也无法容纳,则进行Full GC(老年代GC)。

大对象直接进入老年代,JVM中有个参数配置-XX:PretenureSizeThreshold,令大于这个设置值的对象直接进入老年代,目的是为了避免在Eden和Survivor区之间发生大量的内存复制。

长期存活的对象进入老年代,JVM给每个对象定义一个对象年龄计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳,将被移入Survivor并且年龄设定为1。没熬过一次Minor GC,年龄就加1,当他的年龄到一定程度(默认为15岁,可以通过XX:MaxTenuringThreshold来设定),就会移入老年代。但是JVM并不是永远要求年龄必须达到最大年龄才会晋升老年代,如果Survivor 空间中相同年龄(如年龄为x)所有对象大小的总和大于Survivor的一半,年龄大于等于x的所有对象直接进入老年代,无需等到最大年龄要求。

参考

面试必问之JVM原理
一张图看懂JVM之垃圾回收算法详解


技术讨论 & 疑问建议 & 个人博客

版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议,转载请注明出处!

上一篇下一篇

猜你喜欢

热点阅读