Java学习笔记---垃圾收集算法

在Java堆里存放着Java世界里几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还 “存活”着,哪些已经“死去”(即不可能再被任何途径使用的对象)。

对象存活判定算法

1、引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器就减1;任何时刻计数器为0的对象就是不可能在被使用的。
主流的Java虚拟机里面没有选用引用计数算法来管理内存,其中最主要的原因就是它很难解决对象之间相互循环引用的问题。

2、可达性分析算法

在主流的商用程序语音(Java、C#等)的实现中,都是称通过可达性分析算法来判定对象是否存活的。这个算法的基本思想就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。如图所示,对象object5、object6、object7虽然互相有关联,但是他们到GC Roots是不可达的,所以他们将会被判定为是可回收的对象。


gc-root.jpg
在Java中,可作为GC Roots的对象包括:
1、虚拟机栈(栈帧中的本地变量表)中的引用对象。
2、方法区中类静态属性引用的对象。
3、方法区中常量引用的对象。
4、本地方法栈中JNI(Native方法)引用的对象。





无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。在JDK1.2以前,Java中引用的定义很传统:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。
在JDK1.2以后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)。这四种引用强度一次逐渐减弱。

强引用:

强引用就是指在程序代码中普遍存在的,类似“Object obj=newObject()”这类的引用,只要引用还存在,垃圾收集器永远不会回收掉被引用的对象。

软引用:

软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出之前,将会把这些对象列进回收范围之中进行第二次收回。如果这次回收还没有做够的内存,才会抛出内存溢出异常。

弱引用:

弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集器发生之前。当垃圾会收集器工作时,无论当前的内存是否足够,都会回收掉只被弱引用关联的对象。

虚引用:

虚引用是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对齐生存时间构成影响,也无法通过虚引用来取得一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个通知。

垃圾收集算法

1、标记-清除算法

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

  它的主要不足有两个:
  一是效率问题,标记和清除两个过程的效率都不高;
  另一个是空间问题标记之后会产生大量不连续的内存碎片,
  空间碎片大多可能导致以后再程序运行过程中需要分配较大对象时,
  无法找到足够的连续内存空间而不得不提前出发另一次垃圾收集动作。
标记-清除 算法.jpg

2、复制算法

它将可用的内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后在把已使用过的内存空间一次清理掉。这样就使得每次都是针对整个半区进行内存回收。

  缺点是:将内存缩小一半。
复制算法.jpg

3、标记-整理算法

标记过程与“标记-清除”算法一样,但后续步骤不只是清理对象,而是清理完对象后所有对象都向一端移动,并更新引用指针。

缺点是还是会进行对象移动,成本较高。
好处是不会产生内存碎片。
标记-整理 算法.jpg

4、分代收集算法

当前商业虚拟机的垃圾收集器都采用“分代收集”算法。根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代。在新生代中,每次垃圾收集时都会有大批对象死去,只有少量存活,就用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对他进行分配担保,就必须采用“标记-清理”或者“标记-整理”算法来进行回收。

推荐阅读更多精彩内容