首页 > Java, 挨踢(IT) > Java垃圾回收(2):工作原理

Java垃圾回收(2):工作原理

2014年11月16日 发表评论 阅读评论 794 人阅读    

本文将介绍Java垃圾回收的基础知识,同时将说明它的工作原理。同时,本文还是“Java垃圾回收系列”的第二篇文章。希望您已经阅读过该系列的第一篇文章,“Java垃圾回收入门”

Java垃圾回收是一个自动管理程序所用内存的进程。通过将内存的分配与回收交给Java虚拟机来自动完成,就可以将开发人员从这些繁琐易错的工作中解放出来。

垃圾回收的启动

作为一个自动化的进程,开发人员不需要在代码中显式地启动垃圾回收进程。System.gc()Runtime.gc()是申请垃圾收集器启动垃圾回收的勾子(hook)。

开发人员通过这种请求机制就有机会来请求启动垃圾回收,但是是否启动则由Java虚拟机自己决定。垃圾收集器可以选择拒绝这个请求,所以不保证请求到来之后就肯定进行垃圾回收。是否进行垃圾回收主要是根据堆内存中Eden内存的利用情况。Java虚拟机规范将这个选择权交给了实现,所以更详细的信息还要看具体的实现细节。

现在,我们知道了,垃圾回收不是强制性的。不过,我发现在某些情况下,调用System.gc()还是有一定作用的。你可以通过阅读“System.gc() Invocation – A Suitable Scenario”来了解这些情况。

垃圾回收进程

垃圾回收是一个进程,它将不用的内存空间进行回收,使之可以用于将来创建的实例。

Java垃圾回收进程

  • Eden Space : 当一个实例被创建时,它首先存储在Java堆新生代的Eden Space中。

D瓜哥注:

如果被创建的实例占用内存空间比较大,Eden Space放不下时,会直接存储到老年代中。

注意:如果你不理解这些专业术语,我推荐你先去阅读一下“Java垃圾回收入门”,这篇文章介绍了内存模型,Java虚拟机架构以及常用术语的细节。

  • Survivor Space(S0 和 S1): 作为Minor垃圾回收的一部分,依然存活的对象(就是还被引用的对象)将从Eden Space移动到Survivor Space S0。与此类似,垃圾收集器将扫描S0,并将存活的对象移动到S1
    不在存活的实例(即没有被引用)为了能被回收,将被标记起来。取决于所选垃圾收集器(有四种类型的垃圾收集器可用,我们将在下节详细介绍它们。),垃圾对象可以在标记过程中就被删除,或者是在另外一个进程中执行清除工作。

  • 老年代(Old Generation): 老年代(或者称为永久代)是Java堆中第二个逻辑区域。当垃圾收集器执行Minor GC垃圾回收时,在S1 Survivor Space中依然存活的实例就会晋升到老年代。在S1 Survivor Space中,被解除应用的对象将会被标记回收。

  • Major GC: 对于Java垃圾回收处理过程来说,老年代是对象的整个生命周期中的最后阶段。Major GC是一种垃圾回收过程,它将扫描Java堆中的老年代。如果一个实例不再被引用,那么他们将会被标记起来准备清除,那么他们也将不会继续停留在老年代中。

  • 内存碎片(Memory Fragmentation): 一旦一个实例从Java堆中被删除,那么它原来所处的地方就变为空,并且将来可分配给存活的实例。这些空闲区域可能会零碎地分布在内存区域内。为了更快地为实例分配内存,应该整理一下这些内存碎片。基于垃圾收集器的选择,被回收的内存区域将在垃圾回收过程中被压缩或者通过垃圾收集器的不通的途径来完成。

垃圾回收中的实例终结过程

在清除一个实例,回收内存空间之前,Java的垃圾收集器将调用每个实例的finalize()方法,所以,这些实例将有机会去释放它们持有的任何资源。虽然在内存空间被回收之前,finalize()保证会被执行,但是却没有一个顺序或者时间上的约束。在多个实例之间,执行顺序是不可预知的,他们甚至可能并行执行。程序不应该提前设定好实例之间的顺序,然后使用finalize()方法来释放内存空间。

  • 在终结处理过程中抛出的任何非捕获异常都会被静静地忽略掉,同时实例的终结将会被取消;
  • Java虚拟机规范并没有讨论对虚引用(Weak Reference)的回收,也没有明确的要求。细节留给了具体的实现来决定;
  • 垃圾回收是由守护线程来完成的。

对象合适可以被回收?

有两条原则:

  • 任何实例不能被存活线程可达;
  • 循环引用的实例不能其他任意对象可达;

Java中,有四种不同的引用类型。实例恰当的回收策略取决于引用的类型。

引用类型 回收策略
强引用(Strong Reference) 不适合垃圾回收
软引用(Soft Reference) 可能会被回收,但是这仅仅是最后的选择
弱引用(Weak Reference) 适合回收
虚引用(Phantom Reference) 适合回收

在编译过程中,作为一种性能优化的技术,编译器可以选择将一个实例赋值为null,这样就可以标记一个实例可以被清除。

/**
 * 垃圾回收示例
 * <p/>
 * Coder:D瓜哥,http://www.diguage.com/
 * <p/>
 * Date: 2014-11-16 19:33
 */
public class Animal {
    public static void main(String[] args) {
        Animal lion = new Animal();
        System.out.println("Main is completed.");
    }

    @Override
    protected void finalize() {
        System.out.println("Rest in Peace!");
    }
}

在上面的这个类中,lion实例在初始化语句之后从未使用。那么,Java编译器就会做出一种优化,在初始化语句之后就会这样赋值:lion = null。所以,在Main is completed.输出之前,终结器(Finalizer)也能打印出Rest in Peace!。我们不能很确定地证明这个观点,因为这是由Java虚拟机实现以及运行时内存使用来决定的。然后,我们还能学到一点,如果垃圾收集器看到实例将来不再被引用时,它会选择尽早释放实例。

  • 对于实例什么时候适合垃圾回收有一个更好的例子。实例的所有属性都被存储在寄存器中,寄存器就可以访问,读取这些值。无一例外,这些值将会被写回到实例中。虽然这些值在将来还可能会用到,但是这个实例依然被标记为适合垃圾回事。这就是一个经典的例子,不是吗?
  • 当被赋值为null时,就符合垃圾回收,这是一个简单的例子。对于复杂情况,就像上面描述的那样。这些选择由Java虚拟机实现来做决定。目的是尽可能地减少内存占用,提高响应,增加吞吐量。为了达到这个目的,Java虚拟机实现可以选择更好的方案或算法来回收更多的内存空间。
  • finalize()方法被调用时,Java虚拟机会释放线程中所有的同步锁。

垃圾回事范围的示例程序

/**
 * 垃圾回事示例
 * <p/>
 * Coder:D瓜哥,http://www.diguage.com/
 * <p/>
 * Date: 2014-11-16 20:15
 */
public class GCScope {
    GCScope t;
    static int i = 1;

    public static void main(String args[]) {
        GCScope t1 = new GCScope();
        GCScope t2 = new GCScope();
        GCScope t3 = new GCScope();

        // No Object Is Eligible for GC

        t1.t = t2; // No Object Is Eligible for GC
        t2.t = t3; // No Object Is Eligible for GC
        t3.t = t1; // No Object Is Eligible for GC

        t1 = null;
        // No Object Is Eligible for GC (t3.t still has a reference to t1)

        t2 = null;
        // No Object Is Eligible for GC (t3.t.t still has a reference to t2)

        t3 = null;
        // All the 3 Object Is Eligible for GC (None of them have a reference.
        // only the variable t of the objects are referring each other in a
        // rounded fashion forming the Island of objects with out any external
        // reference)
    }

    @Override
    protected void finalize() {
        System.out.println("Garbage collected from object" + i);
        i++;
    }
}

OutOfMemoryError示例程序

垃圾回收并不能保证绝不出现内存不足。下面这段粗心代码就会导致OutOfMemoryError

import java.util.LinkedList;
import java.util.List;

/**
 * OutOfMemoryError 示例
 * <p/>
 * Coder:D瓜哥,http://www.diguage.com/
 * <p/>
 * Date: 2014-11-16 20:18
 */
public class GC {
    public static void main(String[] main) {
        List l = new LinkedList();
        // Enter infinite loop which will add a String to the list: l on each
        // iteration.
        do {
            l.add(new String("Hello, World"));
        } while (true);
    }
}

D瓜哥注

如果自己电脑内存比较大,运行上面的例子很花很长很长时间才能打印出下面的错误。那么,可以通过设置Java运行内存大小来更快地出现下面的这个存入。具体运行命令如下:

java -Xms2m -Xmx2m GC

对于这两个参数含义不是很清楚地,请看本系列下一篇文章常见的四种Java垃圾收集器中,最后的“垃圾回收优化选项”小节的介绍。

输出:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
  at GC.main(GC.java:19)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:606)
  at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

下一节是本垃圾回收系列的第三篇,我们将介绍几种不同类型的垃圾收集器

原文链接:How Java Garbage Collection Works?



作 者: D瓜哥,https://www.diguage.com/
原文链接:https://wordpress.diguage.com/archives/114.html
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

分类: Java, 挨踢(IT) 标签: , ,