Java8 与 Java7 对象分配内存差异 探索

通过学习“深入理解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代码地址


你可能感兴趣的:(java)