深入理解Java虚拟机-垃圾收集算法

今天讨论的话题是垃圾收集算法,主要介绍一下标记-清除、标记-整理和标记-复制算法,还会基于这些算法讲一下分代垃圾收集算法。

标记-清除算法

首先讲一下标记-清除算法,这是最早出现也是最基础的一种算法。从名字就可以看出该算法分为两个过程:标记和清除。标记可以是标记存活对象,也可以是标记将要回收的对象,清除自然就是将要回收的对象的内存收回。我们通过一张图来了解标记-清除算法。

标记-清除算法

上图中青色的部分是存活对象,棕色部分是可回收的对象,空白部分是未使用的内存。在一次垃圾回收以后,标记为可回收的对象均被清除。这种算法虽然简单,但是有一定局限性。由于可回收对象在内存中的分布不均匀,导致有可能出现当一个对象需要申请较大内存时,没有连续空间分配给对象。

标记-整理算法

标记-整理算法

上图是标记-整理算法的图示,标记-整理算法与标记-清除算法不同的地方在于标记-整理算法是一种移动式的内存回收算法,它会将存活对象统一移动到一块内存并设置边界,回收边界外的所有对象。由于移动对象是一种极为负重的操作,因此在执行该算法时,其他所有的应用程序必须暂停,因此这种停顿被描述为“Stop The World”。

标记-复制算法

标记-复制算法

标记-复制算法将内存分为容量相等的两块,每次只使用其中一块区域,另一块是保留区域。每次垃圾回收,将存活着的对象复制到保留区域中。这种算法适合存活对象占少数的情况,如果存活对象占多数,将会进行大量的内存复制。IBM曾经做过一项调查显示,98%的对象都是“朝生夕灭”,但是这种算法每次保留50%的空间,实际上浪费了大量可用内存。

分代垃圾收集算法

以上三种垃圾收集算法各有优缺点,因此在真实的商用虚拟机设计中,用到了以上算法的思想并设计出了分代算法。在Java虚拟机中,设计者会把Java堆分为新生代和老年代两个区域。其中新生代还会被划分为Eden区和Survivor区,Survivor区还被分为From Survivor和To Survivor。Eden区、From Survivor和To Survivor的内存占比是8:1:1,下图展示了新生代内存的划分。

新生代内存区域

新生代中垃圾回收采用的是标记-复制算法,算法流程如下:

  • 进行第一次垃圾收集时,垃圾收集器将Eden区的存活对象复制到From Survivor区,清空Eden区,存活对象计数加1

  • 再次进行垃圾收集时,将Eden区和From Survivor区的存活对象复制到To Survivor区,清空Eden区和From Survivor区,存活对象计数加1,此时To Survivor变成了From Survivor,From Survivor变成了To Survivor

  • 重复上一步

需要注意的是,有一种极端情况:当Eden区和From Survivor区的存活对象复制到To Survivor区,但内存不够时,会触发内存担保机制,也就是将存活对象直接放入老年代。

新生代中的对象绝大多数都是朝生夕灭的,因此新生代区域是垃圾回收的主要区域。每一次的新生代的垃圾收集过程叫做Minor GC,如果经过一次Minor GC以后对象没有被回收,那么就会为这个对象的标记数加1。对于HotSpot虚拟机,当一个对象的标记数达到了15时,就会被移动到老年代。

但随着时间的推移,老年代的空间也会被用完,这时就会进行一次Full GC。Full GC发生时,新生代和老年代的垃圾都会被收集,因此对系统的影响比较大,应减少Full GC的发生次数。

老年代中的垃圾比较少,因此移动少量对象就可以完成垃圾清理,采用标记-整理算法可以做到回收老年代的垃圾。

猜你喜欢

转载自blog.csdn.net/qq_32273417/article/details/106460641