Java中的垃圾回收机制

总体框图

什么是垃圾

在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行。

失去引用关系的对象,称为垃圾对象

注意:垃圾回收回收的是无任何引用的对象占据的内存空间,而不是对象本身。

垃圾回收算法:标记垃圾对象

1. 引用计数法

根据“垃圾”的定义,直观的做法就是,为每个对象设置一个引用计数器。对对象进行扫描时,如果其引用为0则认为是垃圾,就可以准备回收了。

但是这样做的缺点在于,没有办法消除循环引用的垃圾对象,例如:

public class Main {
    public static void main(String[] args) {
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();
         
        object1.object = object2;
        object2.object = object1;
         
        object1 = null;
        object2 = null;
    }
}
 
class MyObject{
    public Object object = null;
}

虽然指向object1和object2的引用都消除了,但是由于它们之间仍然互相地指向对方,引用计数不为0,所以不会被回收。为了解决这样的问题,有了第二种办法,可达性分析法。

2. 可达性分析法

该方法的基本思想是通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。

垃圾收集算法:确定了哪些是垃圾,进行垃圾回收。

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

最基础的垃圾回收算法,思想简单且容易实现。标记清除算法分为两个阶段:

(1)标记阶段:标记出所有需要被回收的对象;

(2)清除阶段:回收被标记的对象所占的内存空间;

标记清除算法的缺点在于:容易产生内存碎片。碎片太多可能导致后续过程中需要为大对象分配空间时,由于无法找到足够的内存空间而触发新的一次垃圾收集动作。

2. Copying (复制)算法

Copying算法可以解决标记清除算法容易产生内存碎片的缺点,其基本思想是,将可用内存划分为容量大小相等的两块,每次只使用其中的一块。当一块的内存用完了,就将该内存中的所有存活对象复制到另一块中去,然后一次性地将这块内存清理掉。

复制算法的缺点在于:使得能够使用的内存缩减到原来的一半

Copying算法的效率和存活对象的多少有关,如果比较少,效率较高;否则效率将很低。

3. Mark-Compact (标记-整理)算法

为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法,该算法在标记阶段和Mark-Sweep算法一样,但是在完成标记后,它不是直接清理回收对象,而是将存活对象都像一端移动,然后清理掉边界以外的内存。

4. Generational Collection (分代收集)算法

分代收集算法是目前大部分JVM的垃圾收集器采用的算法,其核心思想是,根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次只有少量对象需要被回收;新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点选择合适的收集算法。

新生代:Copying算法,因为新生代中每次垃圾回收都要回收大量对象,存活对象较少,所以复制的代价不高;

老年代:Mark-Compact算法。

注意:在堆区外还有一个代为永久代,它用来存储class类、常量、方法描述等。对永久代的回收主要回收两个部分的内容:废弃常量和无用的类。

程序中建议垃圾回收

1. System.gc()方法

需要注意的是,调用System.gc()也仅仅是一个请求(建议)。JVM接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。

2. finalize()方法

在JVM垃圾回收器收集一个对象之前,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,Java提供了缺省机制来终止该对象心释放资源,这个方法就是finalize()。它的原型为:

protected void finalize() throws Throwable

在finalize()方法返回之后,对象消失,垃圾收集开始执行。原型中的throws Throwable表示它可以抛出任何类型的异常。

之所以要使用finalize(),是存在着垃圾回收器不能处理的特殊情况。假定你的对象(并非使用new方法)获得了一块“特殊”的内存区域,由于垃 圾回收器只知道那些显示地经由new分配的内存空间,所以它不知道该如何释放这块“特殊”的内存区域,那么这个时候java允许在类中定义一个由 finalize()方法。

特殊的区域例如:1)由于在分配内存的时候可能采用了类似 C语言的做法,而非JAVA的通常new做法。这种情况主要发生在native method中,比如native method调用了C/C++方法malloc()函数系列来分配存储空间,但是除非调用free()函数,否则这些内存空间将不会得到释放,那么这个时 候就可能造成内存泄漏。但是由于free()方法是在C/C++中的函数,所以finalize()中可以用本地方法来调用它。以释放这些“特殊”的内存 空间。2)又或者打开的文件资源,这些资源不属于垃圾回收器的回收范围。

换言之,finalize()的主要用途是释放一些其他做法开辟的内存空间,以及做一些清理工作。因为在JAVA中并没有提够像“析构”函数或者类似概念 的函数,要做一些类似清理工作的时候,必须自己动手创建一个执行清理工作的普通方法,也就是override Object这个类中的finalize()方法。例如,假设某一个对象在创建过程中会将自己绘制到屏幕上,如果不是明确地从屏幕上将其擦出,它可能永远 都不会被清理。如果在finalize()加入某一种擦除功能,当GC工作时,finalize()得到了调用,图像就会被擦除。要是GC没有发生,那么 这个图像就会

被一直保存下来。

垃圾回收的不确定性

不知道何时垃圾会被回收,不可控,只是程序建议。

参考资料

[1]  http://blog.csdn.net/zsuguangh/article/details/6429592

[2]  http://www.cnblogs.com/dolphin0520/p/3783345.html

原文地址:https://www.cnblogs.com/harrygogo/p/4641126.html