Java虚拟机(六)内存分配与回收策略

内存分配与回收策略

这次,我们就是用最基本的收集器 Serial来查看内存是怎么分配和回收的吧。首先看Java堆的分区:

1.Eden区

Eden区位于Java堆的年轻代,是新对象分配内存的地方,由于堆是所有线程共享的,因此在堆上分配内存需要加锁。而Sun JDK为提升效率,会为每个新建的线程在Eden上分配一块独立的空间由该线程独享,这块空间称为TLAB(Thread Local Allocation Buffer)。在TLAB上分配内存不需要加锁,因此JVM在给线程中的对象分配内存时会尽量在TLAB上分配。如果对象过大或TLAB用完,则仍然在堆上进行分配。如果Eden区内存也用完了,则会进行一次Minor GC(young GC)。

2.Survival from to

Survival区与Eden区相同都在Java堆的年轻代。Survival区有两块,一块称为from区,另一块为to区,这两个区是相对的,在发生一次Minor GC后,from区就会和to区互换。在发生Minor GC时,Eden区和Survivalfrom区会把一些仍然存活的对象复制进Survival to区,并清除内存。Survival to区会把一些存活得足够旧的对象移至年老代。

3.年老代

年老代里存放的都是存活时间较久的,大小较大的对象,因此年老代使用标记整理算法。当年老代容量满的时候,会触发一次Major GC(full GC),回收年老代和年轻代中不再被使用的对象资源。

注:本节使用JDK均为JDK6。

对象优先在Eden分配

大多数情况下,对象在新生代Eden区分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次MinorGC。

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
//Jdk6
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
*/
public class test {

private static final int _1MB = 1024 * 1024;

public static void main(String[] args) {

byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation4 = new byte[4 * _1MB]; // 出现一次Minor GC
}
}
/*
[GC [DefNew: 6825K->172K(9216K), 0.0045184 secs] 6825K->6316K(19456K), 0.0045424 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4598K [0x33050000, 0x33a50000, 0x33a50000)
eden space 8192K, 54% used [0x33050000, 0x334a27a0, 0x33850000)
from space 1024K, 16% used [0x33950000, 0x3397b060, 0x33a50000)
to space 1024K, 0% used [0x33850000, 0x33850000, 0x33950000)
tenured generation total 10240K, used 6144K [0x33a50000, 0x34450000, 0x34450000)
the space 10240K, 60% used [0x33a50000, 0x34050030, 0x34050200, 0x34450000)
compacting perm gen total 12288K, used 424K [0x34450000, 0x35050000, 0x38450000)
the space 12288K, 3% used [0x34450000, 0x344ba2f8, 0x344ba400, 0x35050000)
ro space 10240K, 55% used [0x38450000, 0x389d3320, 0x389d3400, 0x38e50000)
rw space 12288K, 55% used [0x38e50000, 0x394f6128, 0x394f6200, 0x39a50000)

-XX:SurvivorRatio=8

这个程序分配了三个2MB大小和一个4MB大小的对象,执行分配allocation4时会发生一次MinorGC,这次回收的结果是新生代6825K->172K,总内存占用量几乎没有减少。产生垃圾回收的原因是allocation4不足以在新生代中存放了,而三个2MB的对象又无法放入Survivor空间(1MB),所以只能通过分配担保机制提前转移到老年代去。

所以收集结束后,allocation4被分配到了Eden中,Survivor空闲,老年代被占用6MB。

1
2
def new generation   total 9216K, used 4598K
tenured generation total 10240K, used 6144K

大对象直接进入老年代

使得大对象直接进入老年代,就可以避免在Eden区和两个Survivor区之间来回复制,产生大量的内存复制操作。

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
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* -XX:PretenureSizeThreshold=3145728
*/
public class test {

private static final int _1MB = 1024 * 1024;

public static void main(String[] args) {
byte[] allocation;
allocation = new byte[4 * _1MB]; //直接分配在老年代中C
}
}

/*
Heap
def new generation total 9216K, used 845K [0x33050000, 0x33a50000, 0x33a50000)
eden space 8192K, 10% used [0x33050000, 0x331234d0, 0x33850000)
from space 1024K, 0% used [0x33850000, 0x33850000, 0x33950000)
to space 1024K, 0% used [0x33950000, 0x33950000, 0x33a50000)
tenured generation total 10240K, used 4096K [0x33a50000, 0x34450000, 0x34450000)
the space 10240K, 40% used [0x33a50000, 0x33e50010, 0x33e50200, 0x34450000)
compacting perm gen total 12288K, used 424K [0x34450000, 0x35050000, 0x38450000)
the space 12288K, 3% used [0x34450000, 0x344ba260, 0x344ba400, 0x35050000)
ro space 10240K, 55% used [0x38450000, 0x389d3320, 0x389d3400, 0x38e50000)
rw space 12288K, 55% used [0x38e50000, 0x394f6128, 0x394f6200, 0x39a50000)

可以从 total 10240K, used 4096K ,这一条得出,大对象直接进入到了老年代,不过不是默认进行的,需要使用:-XX:+PrintTenuringDistribution。进行设置。

长期存活的对象直接进入老年代

在进行内存回收时,为了能够判断哪些存活对象是放在新生代还是老年代。有一个默认的评判机制,当在新生代经过了N次MinorGC后,仍然存活,便将其放入老年代。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1
* -XX:+PrintTenuringDistribution
*/
public class test {

private static final int _1MB = 1024 * 1024;

public static void main(String[] args) {
byte[] allocation1, allocation2, allocation3;
allocation1 = new byte[_1MB / 4]; // 什么时候进入老年代决定于XX:MaxTenuringThreshold设置
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
}
}

通过将XX:MaxTenuringThreshold=1设置后,仅仅经过一次MinorGC,便放入老年代。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 438384 bytes, 438384 total
: 5033K->428K(9216K), 0.0044237 secs] 5033K->4524K(19456K), 0.0044527 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 224 bytes, 224 total
: 4772K->0K(9216K), 0.0009550 secs] 8868K->4524K(19456K), 0.0009794 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4234K [0x33050000, 0x33a50000, 0x33a50000)
eden space 8192K, 51% used [0x33050000, 0x334728b0, 0x33850000)
from space 1024K, 0% used [0x33850000, 0x338500e0, 0x33950000)
to space 1024K, 0% used [0x33950000, 0x33950000, 0x33a50000)
tenured generation total 10240K, used 4523K [0x33a50000, 0x34450000, 0x34450000)
the space 10240K, 44% used [0x33a50000, 0x33ebafb0, 0x33ebb000, 0x34450000)
compacting perm gen total 12288K, used 428K [0x34450000, 0x35050000, 0x38450000)
the space 12288K, 3% used [0x34450000, 0x344bb298, 0x344bb400, 0x35050000)
ro space 10240K, 55% used [0x38450000, 0x389d3320, 0x389d3400, 0x38e50000)
rw space 12288K, 55% used [0x38e50000, 0x394f6128, 0x394f6200, 0x39a50000)

那么如果设置为默认值15呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 438384 bytes, 438384 total
: 5033K->428K(9216K), 0.0040562 secs] 5033K->4524K(19456K), 0.0040857 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 224 bytes, 224 total
- age 2: 438176 bytes, 438400 total
: 4772K->428K(9216K), 0.0008210 secs] 8868K->4524K(19456K), 0.0008423 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4662K [0x33050000, 0x33a50000, 0x33a50000)
eden space 8192K, 51% used [0x33050000, 0x334728b0, 0x33850000)
from space 1024K, 41% used [0x33850000, 0x338bb080, 0x33950000)
to space 1024K, 0% used [0x33950000, 0x33950000, 0x33a50000)
tenured generation total 10240K, used 4096K [0x33a50000, 0x34450000, 0x34450000)
the space 10240K, 40% used [0x33a50000, 0x33e50010, 0x33e50200, 0x34450000)
compacting perm gen total 12288K, used 426K [0x34450000, 0x35050000, 0x38450000)
the space 12288K, 3% used [0x34450000, 0x344bb108, 0x344bb200, 0x35050000)
ro space 10240K, 55% used [0x38450000, 0x389d3320, 0x389d3400, 0x38e50000)
rw space 12288K, 55% used [0x38e50000, 0x394f6128, 0x394f6200, 0x39a50000)

动态对象年龄判断

HotSpot虚拟机并不是永远要求对象的年龄必须达到指定值才能够晋升老年代,如果在Survivor空间中,相同年龄所有对象大小的综合大于Survivor空间的一半,年龄大于或等于该年龄的对象,也可以直接进入老年代。

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
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15
* -XX:+PrintTenuringDistribution
*/
public class test {

private static final int _1MB = 1024 * 1024;

public static void main(String[] args) {
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[_1MB / 4]; // allocation1+allocation2大于survivo空间一半
allocation2 = new byte[_1MB / 4];
allocation3 = new byte[4 * _1MB];
allocation4 = new byte[4 * _1MB];
allocation4 = null;
allocation4 = new byte[4 * _1MB];
}
}

/*
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age 1: 700544 bytes, 700544 total
: 5289K->684K(9216K), 0.0044213 secs] 5289K->4780K(19456K), 0.0044653 secs] [Times: user=0.02 sys=0.02, real=0.00 secs]
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 224 bytes, 224 total
: 5028K->0K(9216K), 0.0013033 secs] 9124K->4780K(19456K), 0.0013299 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4234K [0x33050000, 0x33a50000, 0x33a50000)
eden space 8192K, 51% used [0x33050000, 0x33472858, 0x33850000)
from space 1024K, 0% used [0x33850000, 0x338500e0, 0x33950000)
to space 1024K, 0% used [0x33950000, 0x33950000, 0x33a50000)
tenured generation total 10240K, used 4779K [0x33a50000, 0x34450000, 0x34450000)
the space 10240K, 46% used [0x33a50000, 0x33efafc0, 0x33efb000, 0x34450000)
compacting perm gen total 12288K, used 428K [0x34450000, 0x35050000, 0x38450000)
the space 12288K, 3% used [0x34450000, 0x344bb2c8, 0x344bb400, 0x35050000)
ro space 10240K, 55% used [0x38450000, 0x389d3320, 0x389d3400, 0x38e50000)
rw space 12288K, 55% used [0x38e50000, 0x394f6128, 0x394f6200, 0x39a50000)

空间分配担保

前面说到,当大量对象在经过MinorGC之后,仍然存活,但Survivor已经放不下这么多的对象了,那么便直接将对象送入到老年代。

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
/**
* VM参数:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
*/
public class test {

private static final int _1MB = 1024 * 1024;

public static void main(String[] args) {
byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation1 = null;
allocation4 = new byte[2 * _1MB];
allocation5 = new byte[2 * _1MB];
allocation6 = new byte[2 * _1MB];
allocation4 = null;
allocation5 = null;
allocation6 = null;
allocation7 = new byte[2 * _1MB];
}
}
/*
[GC [DefNew: 6825K->172K(9216K), 0.0040796 secs] 6825K->4268K(19456K), 0.0041041 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6651K->172K(9216K), 0.0009841 secs] 10747K->4268K(19456K), 0.0010203 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 2357K [0x33050000, 0x33a50000, 0x33a50000)
eden space 8192K, 26% used [0x33050000, 0x33272560, 0x33850000)
from space 1024K, 16% used [0x33850000, 0x3387b070, 0x33950000)
to space 1024K, 0% used [0x33950000, 0x33950000, 0x33a50000)
tenured generation total 10240K, used 4096K [0x33a50000, 0x34450000, 0x34450000)
the space 10240K, 40% used [0x33a50000, 0x33e50020, 0x33e50200, 0x34450000)
compacting perm gen total 12288K, used 428K [0x34450000, 0x35050000, 0x38450000)
the space 12288K, 3% used [0x34450000, 0x344bb358, 0x344bb400, 0x35050000)
ro space 10240K, 55% used [0x38450000, 0x389d3320, 0x389d3400, 0x38e50000)
rw space 12288K, 55% used [0x38e50000, 0x394f6128, 0x394f6200, 0x39a50000)