通过学习“深入理解Java虚拟机”这本书,在书的92页内存分配这一章节遇见了问题。
代码 运行在Java7上 ,结果如下
/**
* V M 参数 : -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* -XX:+UseSerialGC 设置使用Serial + Serial Old 收集器组合
* -Xms20M -Xmx20M -Xmn10M 限制Java堆大小为20M,10M分配给新生代,10M分配给老年代
* -XX:+PrintGCDetails 打印GC日志
* -XX:SurvivorRatio=8 决定Eden区与一个Survivor区的空间比例是 8 : 1 新生代总可用9M(Eden区 + 1个Survivor区)
*/
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[5 * _1MB]; // 出现Minor GC
}
GC日志
[GC[DefNew: 7805K->533K(9216K), 0.0040666 secs] 7805K->6677K(19456K), 0.0041017 secs] [Times: user=0.02 sys=0.02, real=0.00 secs]
Heap
def new generation total 9216K, used 5903K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
eden space 8192K, 65% used [0x00000000f9a00000, 0x00000000f9f3e5b0, 0x00000000fa200000)
from space 1024K, 52% used [0x00000000fa300000, 0x00000000fa385760, 0x00000000fa400000)
to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)
tenured generation total 10240K, used 6144K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
the space 10240K, 60% used [0x00000000fa400000, 0x00000000faa00030, 0x00000000faa00200, 0x00000000fae00000)
compacting perm gen total 21248K, used 3034K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 14% used [0x00000000fae00000, 0x00000000fb0f7420, 0x00000000fb0f7600, 0x00000000fc2c0000)
从中可以看出allocation1,allocation2,allocation3。由于Minor GC ,直接分配至老年代。allocation4分配在新生代中。
我在Java8中运行程序则出现了截然不同的结果。GC日志如下
[GC (Allocation Failure) [DefNew: 6309K->660K(9216K), 0.0026528 secs] 6309K->4756K(19456K), 0.0026883 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 8066K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 90% used [0x00000000fec00000, 0x00000000ff33b5f0, 0x00000000ff400000)
from space 1024K, 64% used [0x00000000ff500000, 0x00000000ff5a53b8, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 40% used [0x00000000ff600000, 0x00000000ffa00020, 0x00000000ffa00200, 0x0000000100000000)
Metaspace used 3499K, capacity 4502K, committed 4864K, reserved 1056768K
class space used 387K, capacity 390K, committed 512K, reserved 1048576K
问题出现了,为什么新生代被分配了8MB的内存,而老年代被分配了4MB的内存。一脸懵逼,那就只能好好去探索一下内存怎么被吃掉了。
准备一步步DEBUG查看内存分配情况
按照JVM参数的要求,此时新生代与老年代各占10MB,新生代中Eden占8MB。
打断点。在allocation3上,查看当前内存分配情况.
然后找到当前java的进程号,通过进程号,使用jmap进行分析。
cmd命令行使用 jmap -heap 12284 Java7的 结果如下
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 20971520 (20.0MB)
NewSize = 10485760 (10.0MB)
MaxNewSize = 10485760 (10.0MB)
OldSize = 5439488 (5.1875MB)
NewRatio = 2
SurvivorRatio = 8
PermSize = 21757952 (20.75MB)
MaxPermSize = 85983232 (82.0MB)
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 9437184 (9.0MB)
used = 6264960 (5.9747314453125MB)
free = 3172224 (3.0252685546875MB)
66.38590494791667% used
Eden Space:
capacity = 8388608 (8.0MB)
used = 6264960 (5.9747314453125MB)
free = 2123648 (2.0252685546875MB)
74.68414306640625% used
From Space:
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
To Space:
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
tenured generation:
capacity = 10485760 (10.0MB)
used = 0 (0.0MB)
free = 10485760 (10.0MB)
0.0% used
Perm Generation:
capacity = 21757952 (20.75MB)
used = 3117424 (2.9730072021484375MB)
free = 18640528 (17.776992797851562MB)
14.32774555252259% used
Java8的 结果如下
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 20971520 (20.0MB)
NewSize = 10485760 (10.0MB)
MaxNewSize = 10485760 (10.0MB)
OldSize = 10485760 (10.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 9437184 (9.0MB)
used = 6673184 (6.364044189453125MB)
free = 2764000 (2.635955810546875MB)
70.71160210503473% used
Eden Space:
capacity = 8388608 (8.0MB)
used = 6673184 (6.364044189453125MB)
free = 1715424 (1.635955810546875MB)
79.55055236816406% used
From Space:
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
To Space:
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
tenured generation:
capacity = 10485760 (10.0MB)
used = 0 (0.0MB)
free = 10485760 (10.0MB)
0.0% used
Java7的运行结果中,Eden被占用了5.9MB左右,刚好可以分配下一个,所以没有出现这个问题。
在Java8的运行结果中,可以看到Eden被占用了6.36MB。
现在可以看出问题在哪里了,因为Eden只有8MB,而当前已经占用了6.36MB,分配allocation3是Eden内存不够。Survivor区域内存也放不下,所以将当前Eden内对象放入老年代。此时allocation1,allocation2被放入老年代,allocation3在Eden中。然后继续分配allocation4 可以直接放入Eden中,所以产生了这样的现象。
没想到就差了这一点点内存就出现了这样的问题。
接下来还有一个疑问,我当前断点在allocation3,allocation3并没有分配内存,应该占用的只有4MB才对,为什么出现了6MB。就是刚开始分配内存时,为什么会多出那么2MB多呢。
然后把代码全删掉,直接运行,查看内存分配情况,
Java1.7 结果如下
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 20971520 (20.0MB)
NewSize = 10485760 (10.0MB)
MaxNewSize = 10485760 (10.0MB)
OldSize = 5439488 (5.1875MB)
NewRatio = 2
SurvivorRatio = 8
PermSize = 21757952 (20.75MB)
MaxPermSize = 85983232 (82.0MB)
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 9437184 (9.0MB)
used = 2070624 (1.974700927734375MB)
free = 7366560 (7.025299072265625MB)
21.941121419270832% used
Eden Space:
capacity = 8388608 (8.0MB)
used = 2070624 (1.974700927734375MB)
free = 6317984 (6.025299072265625MB)
24.683761596679688% used
From Space:
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
To Space:
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
tenured generation:
capacity = 10485760 (10.0MB)
used = 0 (0.0MB)
free = 10485760 (10.0MB)
0.0% used
Perm Generation:
capacity = 21757952 (20.75MB)
used = 3117376 (2.97296142578125MB)
free = 18640576 (17.77703857421875MB)
14.327524943524097% used
Java1.8结果如下:
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 20971520 (20.0MB)
NewSize = 10485760 (10.0MB)
MaxNewSize = 10485760 (10.0MB)
OldSize = 10485760 (10.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 9437184 (9.0MB)
used = 2478848 (2.364013671875MB)
free = 6958336 (6.635986328125MB)
26.26681857638889% used
Eden Space:
capacity = 8388608 (8.0MB)
used = 2478848 (2.364013671875MB)
free = 5909760 (5.635986328125MB)
29.5501708984375% used
From Space:
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
To Space:
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
tenured generation:
capacity = 10485760 (10.0MB)
used = 0 (0.0MB)
free = 10485760 (10.0MB)
0.0% used
这下也清楚了,运行程序默认会占用一部分空间。
结论
书中运行环境为1.7,本机使用1.8。在初始化中,1.7占用了1.97MB内存,1.8占用了2.36MB内存。就是差了0.3MB的空间,导致提前触发了一次Minor GC。
GitHup代码地址