常用垃圾收集算法——《深入理解Java虚拟机》笔记

原创 2016年08月28日 22:17:42

概述

垃圾收集器(Garbage Collection, GC)的历史要比Java久远,且并非Java独有,GC主要完成以下三件事情:

  1. 哪些内存需要回收
  2. 什么时候回收
  3. 如何回收

对于Java内存运行时区域的各个部分,程序计数器、虚拟机栈、本地方法栈3个线程私有区域是随线程而生,又随线程而灭,因此这几个区域的内存分配和回收都具备确定性,不需要考虑垃圾回收的问题。而Java堆和方法区这两个线程共享区的内存是动态分配的,因此垃圾收集器主要关注的是这部分的内存,这里也是垃圾回收的主战场。

如何判断对象死亡

引用计数算法

原理:给对象添加一个引用计数器,当有一个地方引用它时,计数器加1,当引用结束时,计数器减1,任何时候当引用计数为0时,则认为该对象没有被使用,可以被回收。

引用计数算法实现简单,效率也很高,也有很多技术中都使用该算法来管理内存。但Java虚拟机却没有使用该算法,最主要的原因它很难解决对象之间互相循环引用的问题。例如下面示例代码:

public class ReferenceCountingGC {
    public Object instance = null;

    public static void testGC() {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;

        objA = null;
        objB = null;

        System.gc();
    }
}

上述代码中objA与objB互相引用,如果Java虚拟机采用引用计数算法,发生GC时,objA、objB将无法被回收。测试可知,上述Java代码在GC时是可以回收内存的,因此可从侧面证明Java虚拟机并非用的引用计数算法。

可达性分析算法

其实在Java、C#等主流语言中,使用的都是可达性分析(Reachability)算法来判定对象是否存活的。该算法基本思路是通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径叫做引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,用图论原理来说,即GC Roots到这个对象是不可达的,则以此判断该对象是不可用的。

可达性分析原理示意图(来自Google I/O)

在Java语言中,可作为GC Roots的对象有以下几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中JNI引用的对象

常用的垃圾收集算法

标记-清除算法

标记-清除(Mark-Sweep)算法是最基础的垃圾收集算法,其分为两个阶段:首先标记出所需要回收的对象,然后在标记完成后统一回收所有被标记的对象。

标记-清除算法的不足:

  1. 效率问题,标记和清除两个过程的效率都不高
  2. 空间问题,标记清除后会产生大量不连续的内存碎片,空间碎片太多可能导致以后要为大对象分配内存时,如果找不到足够的连续内存而不得不触发另一次垃圾收集动作。

标记-清除算法原理示意图(来自《深入理解Java虚拟机》)

复制算法

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

复制算法原理示意图(来自《深入理解Java虚拟机》)

现代商业虚拟机都采用这种收集算法来回收新生代,但按上述原理,将内存缩小为原来的一半使用,这种代价有点太大了。IBM研究证明,98%的对象是“朝生夕死”,所以不必按1:1来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中一块Survivor。当回收内存时,将Eden和第一块Survivor中还存活的对象一次性地复制到第二块Survivor中,然后清理掉Eden和第一块Survivor空间。HotSpot虚拟机默认Eden与Survivor空间的大小比例为8:1,一旦第二块Survivor不足以容纳Eden与第一块Survivor复制过来的存活对象时,这些对象将通过分配担保机制进入老年代。

标记-整理算法

复制收集算法在对象存活率较高时就要频繁进行复制操作,导致效率变低,因此在老年代一般不选用这种算法。根据老年代的特点,有人提出了标记-整理(Mark-Compact)算法,标记过程跟标记-清除算法的一样,但后续步骤不是直接将可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。

标记-整理算法原理示意图(来自《深入理解Java虚拟机》)

分代收集算法

当前虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法。算法思想是跟根据对象存活周期的不同将内存划分为几块,一般将Java堆划分为新生代和老年代,这样可以根据各个年代的特点采用最合适的垃圾收集算法。

在新生代中,每次垃圾收集时都有大量对象死亡,只有少量存活,因此优选复制算法,这样只需要付出少量对象的复制成本就可以完成收集;而对于老年代,因为对象存活率高,且没有额外的空间对它进行分配担保,就更适合使用标记-清理或标记-整理算法来进行回收。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Android 动画animation 深入分析

Android 动画animation 深入分析 前言:本文试图通过分析动画流程,来理解android动画系统的设计与实现,学习动画的基本原则,最终希望能够指导动画的设计。 0 本文中用到的一些...

深入分析UI 上层事件处理核心机制 Choreographer

深入分析UI 上层事件处理核心机制 Choreographer 结论写在前面:Choreographer就是一个消息处理器,根据vsync 信号 来计算frame,而计算frame的方式就是处理三种...

垃圾收集算法与内存分配策略--《深入理解Java虚拟机》学习笔记

垃圾收集算法与内存分配策略–《深入理解Java虚拟机》学习笔记一、对象存活算法判定1、何为引用 如果reference类型的数据中存储的数值代表另一块内存的起始地址,则称这块内存代表着一个引用;有一...

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

当对象判定为"已死"状态,虚拟就要采取一定的手段将这些对象从内存中移除,即回收垃圾,回收过程有采用一定的算法。如下是一些主要的垃圾收集算法: 1.标记-清除算法    该算法是最基础的算法,分为“...

Java虚拟机学习笔记(2)——垃圾收集算法

上一篇文章学习了JVM基本的内存模型,JVM内存区域可以分为:方法区、堆区、虚拟机栈、本地方法栈、程序计数器。方法区和堆区属于线程共享区域, 而虚拟机栈、本地方法栈、程序计数器则属于线程隔离区域。其中...

《深入理解java虚拟机》读书笔记四 【垃圾收集算法和垃圾收集器】

垃圾收集算法 垃圾收集器 内存分配

[深入理解Java虚拟机]第三章 垃圾收集算法

标记-清除算法最基础的收集算法是“标记-清除” (Mark-Sweep ) 算法,如同它的名字一样 ,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的...

5.《深入理解Java虚拟机》垃圾收集算法思想

前面一篇博文讲了怎么判断哪些对象是可以被收集的,确定了哪些对象可以被回收之后,自然需要研究的是如何对对象进行回收。下面主要介绍几种算法的思想以及其发展过程: 首先普及两个概念: 新生代:主要用来存...

[深入理解Java虚拟机]第三章 HotSpot的垃圾收集算法实现

枚举根节点从可达性分析中从GC Roots节点找引用链这个操作为例,可作为GC Roots的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)中 ,现在很多应用仅仅方...

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

概述当前的商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新...
  • FX_SKY
  • FX_SKY
  • 2016年05月01日 19:38
  • 537
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:常用垃圾收集算法——《深入理解Java虚拟机》笔记
举报原因:
原因补充:

(最多只允许输入30个字)