垃圾收集算法

来源:互联网 发布:钻石净度分级标准知乎 编辑:程序博客网 时间:2024/06/13 08:43

垃圾收集器与内存分配策略参考目录:

1.判断Java 对象实例是否死亡
2. Java 中的四种引用
3.垃圾收集算法
4. Java9中的GC 调优
5.内存分配与回收策略

这篇博文主要介绍虚拟机中的几种垃圾回收算法(当然进行垃圾回收的地方主要是指Java 堆)。

标记 - 清除算法(Mark-Sweep)

      标记清除算法也是最基础的算法,和它的名字一样,在进行垃圾回收的时候主要分为“标记阶段” 和“清除阶段” : 首先标记出所需要回收的对象,在标记完成后进行统一回收所有被标记的对象。其他后面的几种算法都是在它的基础上对它做出的改进。标记清除算法在效率是上存在着一定的问题,原因是标记过程与清除过程的效率都不高;另外一方面是空间问题,原因是在标记清除之后会存在大量的不连续的内存碎片,这种情况导致在程序运行过程中分配大对象时因为无法找到连续的足够的内存空间不得不提前进行一次垃圾收集动作。
这里写图片描述

复制算法(Coping)

      为了解决回收效率的问题,复制算法出现了。根据对象在Java 堆中创建的特点: 一般对象在新生代中分配内存,并且新生代中的对象98% 一般都是“朝生夕死” 的。根据这个特点将新生代划分成一块较大的Eden 区和两块小的Survivor(from 和 to) 区,每次使用Eden 和其中的一块Survivor 区。在进行垃圾回收时将Eden 区和一块Survivor 中还存活的对象一次性的复制到另一块Survivor 区上,然后清理掉刚才使用的Eden 区 和Survivor 区的内存空间。但是我们想在每次进行垃圾回收的时候不就有一块空间处于空闲状态了吗,这样不就使其中的一块Survivor 空间被浪费了吗?事实上Eden 区和Survivor 区的大小比例是8 : 1,也就是Eden 区占据了新生代中4/5 的内存空间,而两个Survivor 区只占到1/5 的内存空间。每次在进行垃圾回收的时候使用Eden 区 和其中的一块Survivor 区,那么就只有1/10 的空间被浪费,这点是可以接受的。但是我们也不能保证在进行垃圾回收之后另一块Survivor 区能够为所有存活下来的对象分配空间,当Survivor 空间不够用时这时就需要依赖其他的内存空间(老年代)进行分配担保。下面是复制算法过程示意图:
这里写图片描述
      其中关于Eden 区域Survivor 区内存占比的问题我们可以通过打印GC 日志进行验证。这里我只拷贝了Java 堆中的内存放分配关系,我们可以看出新生代共有 38400K ,Eden 区占据了33280K,38400 - 33280 = 5120K。这5120K 就是两块Survivor区(其中一块叫from 区 一块叫 to 区,to 区就是在进行垃圾回收时空闲的一块,在一次回收完成时,from 区与to 区互换) 总共的内存大小,。通过打印结果看出它将两块Survivor 区的大小算在一起了,不要以为每一块Survivor 区都有5120K 空间的大小。下面就来计算一下Eden 区 是不是占了新生代总内存的4/5,通过33280 / 38400 = 86.7%,事实上比80% 的内存空间还要大一些,博主的jdk 是1.8,不知道是不是这个缘故,不过也没有没有关系啦,你只要能理解在使用复制算法进行垃圾回收的时候并没有很大的内存空间被浪费就好了。

Heap PSYoungGen      total 38400K, used 998K [0x00000000d5f00000, 0x00000000d8980000, 0x0000000100000000)  eden space 33280K, 3% used [0x00000000d5f00000,0x00000000d5ff9b20,0x00000000d7f80000)  from space 5120K, 0% used [0x00000000d7f80000,0x00000000d7f80000,0x00000000d8480000)  to   space 5120K, 0% used [0x00000000d8480000,0x00000000d8480000,0x00000000d8980000) ParOldGen       total 87552K, used 4705K [0x0000000081c00000, 0x0000000087180000, 0x00000000d5f00000)  object space 87552K, 5% used [0x0000000081c00000,0x0000000082098400,0x0000000087180000)

标记-整理算法(Mark - Compat)

      通过上面了解我们可以知道使用复制算法时确实效果比标记-清除算法好很多,但是它也有一些局限,就是当处理一些存活率较高的对象时它的效率就会受到影响,当Survivor 区内存不够时就会在老年代上进行内存分配,所以可见对老年代进行必要的垃圾回收也是很重要的。根据老年代的点有人提出了一种“标记-整理”的 算法机制,标记的过程与“标记-清除”算法中实现的过程一样,但是后面的步骤不是对可回收对象进行清除而是让所有存活的对象向某一端移动,然后直接清理掉端边界以外的内存。
这里写图片描述

分代收集算法(Generational Collection)

      其实这种算法并没有什么其他特别的地方,只是根据对象的存活周期将内存划分为几块。一般还是划分为新生代和老年代,然后根据各个区的特点选择适当的垃圾回收算法进行垃圾回收。由于新生代每次进行垃圾回收的时候都会有大批的对象死去,所以就使用“复制算法”。根据老年代对象存活时间长的特点就必须使用“标记-整理”和“标记-清除”算法进行垃圾回收。

      本篇博文中的有关垃圾回收的相关图片均来自使这位朋友,感谢!

                                                                                                            参考书籍:
                                                                                                                《深入理解Java 虚拟机》周志明 著