本教程是为了理解基本的Java垃圾回收以及它是如何工作的。这是垃圾回收教程系列的第二部分。希望你已经读过了第一部分:《Java 垃圾回收介绍》。
Java 垃圾回收是一项自动化的过程,用来管理程序所使用的运行时内存。通过这一自动化过程,JVM 解除了程序员在程序中分配和释放内存资源的开销。
作为一个自动的过程,程序员不需要在代码中显示地启动垃圾回收过程。System.gc()
和Runtime.gc()
用来请求JVM启动垃圾回收。
虽然这个请求机制提供给程序员一个启动 GC 过程的机会,但是启动由 JVM负责。JVM可以拒绝这个请求,所以并不保证这些调用都将执行垃圾回收。启动时机的选择由JVM决定,并且取决于堆内存中Eden区是否可用。JVM将这个选择留给了Java规范的实现,不同实现具体使用的算法不尽相同。
毋庸置疑,我们知道垃圾回收过程是不能被强制执行的。我刚刚发现了一个调用System.gc()
有意义的场景。通过这篇文章了解一下适合调用System.gc() 这种极端情况。
垃圾回收是一种回收无用内存空间并使其对未来实例可用的过程。
Eden 区:当一个实例被创建了,首先会被存储在堆内存年轻代的 Eden 区中。
注意:如果你不能理解这些词汇,我建议你阅读这篇 垃圾回收介绍 ,这篇教程详细地介绍了内存模型、JVM 架构以及这些术语。
Survivor 区(S0 和 S1):作为年轻代 GC(Minor GC)周期的一部分,存活的对象(仍然被引用的)从 Eden 区被移动到 Survivor 区的 S0 中。类似的,垃圾回收器会扫描 S0 然后将存活的实例移动到 S1 中。
(译注:此处不应该是Eden和S0中存活的都移到S1么,为什么会先移到S0再从S0移到S1?)
死亡的实例(不再被引用)被标记为垃圾回收。根据垃圾回收器(有四种常用的垃圾回收器,将在下一教程中介绍它们)选择的不同,要么被标记的实例都会不停地从内存中移除,要么回收过程会在一个单独的进程中完成。
老年代: 老年代(Old or tenured generation)是堆内存中的第二块逻辑区。当垃圾回收器执行 Minor GC 周期时,在 S1 Survivor 区中的存活实例将会被晋升到老年代,而未被引用的对象被标记为回收。
老年代 GC(Major GC):相对于 Java 垃圾回收过程,老年代是实例生命周期的最后阶段。Major GC 扫描老年代的垃圾回收过程。如果实例不再被引用,那么它们会被标记为回收,否则它们会继续留在老年代中。
内存碎片:一旦实例从堆内存中被删除,其位置就会变空并且可用于未来实例的分配。这些空出的空间将会使整个内存区域碎片化。为了实例的快速分配,需要进行碎片整理。基于垃圾回收器的不同选择,回收的内存区域要么被不停地被整理,要么在一个单独的GC进程中完成。
在释放一个实例和回收内存空间之前,Java 垃圾回收器会调用实例各自的 finalize()
方法,从而该实例有机会释放所持有的资源。虽然可以保证 finalize()
会在回收内存空间之前被调用,但是没有指定的顺序和时间。多个实例间的顺序是无法被预知,甚至可能会并行发生。程序不应该预先调整实例之间的顺序并使用 finalize()
方法回收资源。
Java 中有不同的引用类型。判断实例是否符合垃圾收集的条件都依赖于它的引用类型。
引用类型 | 垃圾收集 |
---|---|
强引用(Strong Reference) | 不符合垃圾收集 |
软引用(Soft Reference) | 垃圾收集可能会执行,但会作为最后的选择 |
弱引用(Weak Reference) | 符合垃圾收集 |
虚引用(Phantom Reference) | 符合垃圾收集 |
在编译过程中作为一种优化技术,Java 编译器能选择给实例赋 null
值,从而标记实例为可回收。
1
2
3
4
5
6
7
8
9
10
|
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 输出之前, finalize 函数也能够打印出 'Rest in Peace!'
。我们不能证明这确定会发生,因为它依赖JVM的实现方式和运行时使用的内存。然而,我们还能学习到一点:如果编译器看到该实例在未来再也不会被引用,能够选择并提早释放实例空间。
finalize()
方法被调用时,JVM 会释放该线程上的所有同步锁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
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++;
}
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();
// 没有对象符合GC
t1.t = t2;
// 没有对象符合GC
t2.t = t3;
// 没有对象符合GC
t3.t = t1;
// 没有对象符合GC
t1 =
null
;
// 没有对象符合GC (t3.t 仍然有一个到 t1 的引用)
t2 =
null
;
// 没有对象符合GC (t3.t.t 仍然有一个到 t2 的引用)
t3 =
null
;
// 所有三个对象都符合GC (它们中没有一个拥有引用。
// 只有各对象的变量 t 还指向了彼此,
// 形成了一个由对象组成的环形的岛,而没有任何外部的引用。)
}
protected
void
finalize() {
System.out.println(
"Garbage collected from object"
+ i);
i++;
}
|
GC并不保证内存溢出问题的安全性,粗心写下的代码会导致 OutOfMemoryError
。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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
);
}
}
|
输出:
1
2
3
4
|
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)
|
接下来是垃圾收集系列教程的第三部分,我们将会看到常用的 不同 的Java垃圾收集器。
原文链接: javapapers 翻译: ImportNew.com - 伍翀