说明:这篇文章来翻译来自于Javapapers 的How Java Garbage Collection Works
这部分教程是为了理解Java垃圾回收的基础以及它是如何工作的。这是垃圾回收系列教程的第二部分。希望你已经读过第一部分的Java垃圾回收介绍。
Java垃圾回收是一个用来管理程序运行时内存的自动的进程。因为它是JVM自动来处理的,所以它将程序员从程序的内存资源的分配和释放中解放出来。
作为一个自动的进程,程序员不需要在代码中显示的去启动垃圾回收进程。System.gc()
和Runtime.gc()
用来提供向JVM发出请求去开启垃圾回收进程。
尽管这个请求机制给程序员提供了一个机会去开启进程,但是主动权还是在JVM中。它可以选择区拒绝这个请求而且它也不保证这些调用会做垃圾回收工作。这个决定是由JVM根据在堆内存中的Eden区是否可用做出来的。JVM规范把这个留给了具体的实现,所以具体的实现还需要看实现规范说明。
毫无疑问我们知道垃圾回收经常不能够被强迫的。我偶尔发现一种场景就是当在调用System.gc()
时起作用了。通过这篇文章(System.gc() invocation is applicable)我们可以了解这个难以发现的实例。
垃圾回收是一个释放无用的内存区并且使它能够被将来的实例能够使用的一个进程。
Eden Sapce:当实例被创建的时候,它首先被存放到堆内存中的年轻代的Eden区。
说明:如果你不理解这些名词,我建议你去浏览垃圾回收介绍教程,这里面详细的介绍了内存模型、JVM结构还有这些名词。
Surivor Space(S0 and S1):作为minor
垃圾回收的一部分,仍然存活的对象(仍然被引用着)从Eden 区移动到survivor 区S0.于此类似的是垃圾回收器扫描S0
并且把存活的实例移到S1
。
不再存活(没有引用)的实例被垃圾回收器打上标记。和垃圾回收器有关,是否选择标记实例是在运行的时候从内存中移除还是在在不同的进程中由垃圾回收进行来做。
Old Generation:老年代是堆内存的第二个逻辑部分。当垃圾回收器在进行minor GC
时候,在S1
中仍然仍然存活的会被移动老年代中。在S1中不再被引用的将被标记回收。
Major GC:相对于垃圾回收进程,老年代是实例生命周期的最后一步。Major GC
是垃圾回收进程中扫描堆内存中来年代部分的。如果实例不再被引用,然后他们会被标记回收而如果还仍然被引用着他们将继续留在老年代中。
Memory Fragmentation(内存碎片):一旦实例从堆内存被删除以后,它所在的区域就变成了工而且能够被将来存活的实例所使用。这些空的区域在整个内存区域将以碎片的形式存在。对于更快地分配实例这些应该得到整理。决定于垃圾回收器的选择,被释放的内存区是在运行的时候被压缩还是在处理完成之后再处理。
在回收实例和释放内存空间之前,垃圾回收器会调用finalize()
方法以便实例能够有机会释放它所拥有的任何资源。尽管finalize()
方法在释放内存空间之前肯定会被调用,但是这个没有顺利和时间要求指出来。在多个实例之间的顺序是不能够提前决定的,因为他们可能并行的发生。程序不应该在调用finalize()
的时候不应该指定它在实例和释放资源的顺序。
有Java中有不同种类的引用。能否被垃圾回收取决于它所拥有的引用类型
Reference | Garbage Collection |
Strong Reference | Not eligible for garbage collection |
Soft Reference | Garbage collection possible but will be done as a last option |
Weak Reference | Eligible for Garbage Collection |
Phantom Reference | Eligible for Garbage Collection |
在编译过程中,作为一种优化技术Java编译器能够选择将null
值分配给一个实例,以便这个实例能够被标记回收。
class Animal {
public static void main(String[] args) {
Animal lion = new Animal();
System.out.println("Main is completed.");
}
protected void finalize() {
System.out.println("Rest in Peace!");
}
}
在上面这个类中,lion
实例在实例化行从来没有被使用。所以Java编译器作为优化的策略可以将赋值lion=null
实例化所在行。所以,尽管在SOP's
输出之前,初始化器可能打印出Reset in Peace!
。我们不能确切的表明它,因为它依赖与JVM具体的实现以及在运行时的内存使用。但是能够获悉的是,如果编译器能够察觉它在将来不会被引用了它可以在程序中选择去释放这些实例。
finalize()
方法被调用的时候,JVM会释放在这个线程中的同步锁。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)
}
protected void finalize() {
System.out.println("Garbage collected from object" + i);
i++;
}
Example Program for GC OutOfMemoryError
Garbage collection does not guarantee safety from out of memory issues. Mindless code will lead us to OutOfMemoryError.
import java.util.LinkedList;
import java.util.List;
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);
}
}
输出:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.LinkedList.linkLast(LinkedList.java:142)
at java.util.LinkedList.add(LinkedList.java:338)
at com.javapapers.java.GCScope.main(GCScope.java:12)