Java虚拟机学习 - 垃圾收集算法

来源:互联网 发布:淘宝宝贝自动下架 编辑:程序博客网 时间:2024/06/01 09:50

跟踪收集器

跟踪收集器采用的为集中式的管理方式,全局记录对象之间的引用状态,执行时从一些列GC  Roots的对象做为起点,从这些节点向下开始进行搜索所有的引用链,当一个对象到GC  Roots 没有任何引用链时,则证明此对象是不可用的。
下图中,对象Object6、Object7、Object8虽然互相引用,但他们的GC Roots是不可到达的,所以它们将会被判定为是可回收的对象。

可作为GC Roots 的对象包括:
虚拟机栈(栈帧中的本地变量表)中的引用对象。
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI的引用对象。

主要有复制、标记清除、标记压缩三种实现算法。 

1. 标记 - 清除算法 

标记清除算法是最基础的收集算法,其他收集算法都是基于这种思想。标记清除算法分为“标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象。

它的主要缺点:

①.标记和清除过程效率不高 

②.标记清除之后会产生大量不连续的内存碎片。


2. 复制算法 

它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,不会产生碎片等情况,只要移动堆订的指针,按顺序分配内存即可,实现简单,运行高效。

主要缺点:

内存缩小为原来的一半。

                            


3. 标记  - 整理算法

标记操作和“标记-清除”算法一致,后续操作不只是直接清理对象,而是在清理无用对象完成后让所有存活的对象都向一端移动,并更新引用其对象的指针。

主要缺点:

在标记-清除的基础上还需进行对象的移动,成本相对较高,好处则是不会产生内存碎片。


引用计数收集器

引用计数收集器采用的是分散式管理方式,通过计数器记录对象是否被引用。当计数器为0时说明此对象不在被使用,可以被回收。

主要缺点:

循环引用的场景下无法实现回收,例如下面的图中,ObjectC和ObjectB相互引用,那么ObjectA即便释放了对ObjectC、ObjectB的引用,也无法回收。sunJDK在实现GC时未采用这种方式。



JVM垃圾收集算法的具体实现有很多种,本文只是介绍实现这些垃圾收集算法的三种思想和发展过程。所有的垃圾收集算法的具体实现都是遵循这三种算法思想而实现的。

1.标记-清除算法

标记-清除(Mark-Sweep)算法是最基础的垃圾收集算法。正如其名字描述的那样,该算法分为两个阶段:“标记”和“清除”。首先标记出所有可以被回收的对象,然后经过一轮垃圾回收将所有被标记的对象的内存空间释放,即清除可被回收的对象。标记-清除算法的执行过程如下图所示:

mark-sweep

该算法的优点是逻辑简单,最初想到的垃圾收集算法就是这种。之后的收集算法都是针对该算法的不足进行改进而产生的新算法。该算法主要存在两种不足:一是标记和清除两个阶段都比较低效;二是这种算法虽然收回了内存,但是容易导致内存中有大量的不连续的小的内存区域(内存碎片)。当有一个大的对象需要创建时,可能出现虽然总的可用内存很多,但是没有一个连续的大的内存空间来存放大对象的情况,从而导致了过早的再一次GC。

2.复制算法

为了解决标记-清除算法效率低以及内存碎片的问题,出现了复制(copying)算法。该算法的思想是将整个内存空间分为两个大小相同的空间。同一时期只在一个内存空间上创建对象,当该部分内存满了之后,执行垃圾回收。垃圾回收的过程:首先将不能回收的依然可用的对象复制到另一个内存空间上,然后将之前的一个内存空间直接清空。经过该轮垃圾回收后,之前的一个内存空间对象被全部清空了,活着的对象被复制到了另一个内存空间上,此时再创建对象就在另一个内存空间上分配空间了。复制算法的执行过程如下图所示:

copying

该算法的优点是逻辑简单,执行高效。但缺点是实际只使用了一半的可用内存,浪费了一半的内存。在商业的JVM中一般使用复制算法来回收新生代的内存空间。因为新生代中的对象大部分都是使用一次就被抛弃的对象,所以新生代中的对象的存活率是很低的。绝大多数情况下符合80:20的原则。复制算法的具体实现上,是将新生代内存区域划分为8:1:1的三块区域,8的区域叫做Eden区,两个1都叫做Survivor区,分配内存时,首先在一个Survivor区和Eden区分配,当垃圾回收时,将上述两个区的活着的对象复制到另一个Survivor区,然后将Eden区和原来的那个Survivor区清空。这样的话使用了整个可用内存的90%,大大减小了浪费。当然,会出现垃圾回收时一个Survivor区装不下所有存活对象的情况(很少发生),当出现时,就会进行分配担保,由其他内存区域(一般是老年代)来存放溢出的对象。

3.标记-整理算法

复制算法对于对象存活率很低的情况是高效的,但是当对象的存活率非常高时,就变得非常低效了。在老年代中,对象的存活率很高,所以不能使用复制算法。于是根据老年代的对象特点,提出了标记-整理(Mark-Compact)算法。标记-整理算法也分为两个阶段:标记和整理。第一个阶段与标记-清除算法一样:标记出所有可以被回收的对象。第二个阶段不再是简单的清除无用对象的空间,而是将后面的活着的对象依次向前移动。将所有的活着的对象都移动成内存空间中前段连续一个区域,之后的连续的区域都是可分配的没有使用的内存空间。标记-整理算法的执行过程如下图所示:

mark-compact

4.小结:分代收集思想

当前商业虚拟机中一般采用“分代收集算法”。分代收集算法是根据对象的特点将内存空间分成不同的区域(即不同的代),对每个区域使用合适的收集算法。在JVM中一般分为新生代和老年代,新生代中对象的存活率比较低,使用复制算法简单高效;在老年代中,由于对象的存活率较高,所以一般采用标记-整理算法。 
对于哪种算法最好,我只能说,没有最好的算法,只有最适合某一种场景的某一种算法。正是因为垃圾收集的对象各有特点,才诞生了多种不同的收集算法。就像语言一样,没有必要去争论Java最好,C++最强。每一个语言的存在必然有其使用场景和适合的地方。兼听则明,多多学习就是了。

0 0
原创粉丝点击