一、jvm垃圾回收器
jvm的垃圾回收器有serial收集器、parnew收集器、parallel scavenge收集器、serial old 收集器、parallel old收集器、cms收集器、g1收集器
HotSpot虚拟机所包含的收集器:
图中展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集器。
新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:CMS、Serial Old、Parallel Old
整堆收集器: G1
二、jvm内存分配担保机制
1、指定Serial+Serial Old作为jvm垃圾回收器
jvm启动参数如下:
-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
其中10M分配给新生代,另外10M分配给老生代。新生代中eden和survivor区的比例为8:1,eden区所分配内存为8M,两个survivor区分别为1M,新生代总可用空间为9M。
测试代码如下:
package handlepromotion; public class HandlePromotionFailure { private static final int _1MB = 1024 * 1024; public static void testHandlePromotionFailure() { byte[] allocation1 = new byte[2* _1MB]; byte[] allocation2 = new byte[2* _1MB]; byte[] allocation3 = new byte[2* _1MB]; byte[] allocation4 = new byte[4* _1MB]; } public static void main(String[] args) { testHandlePromotionFailure(); } }
测试结果:
[GC (Allocation Failure) [DefNew: 7127K->526K(9216K), 0.0062127 secs] 7127K->6670K(19456K), 0.0062962 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
def new generation total 9216K, used 4704K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
from space 1024K, 51% used [0x00000000ff500000, 0x00000000ff583918, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation total 10240K, used 6144K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 60% used [0x00000000ff600000, 0x00000000ffc00030, 0x00000000ffc00200, 0x0000000100000000)
Metaspace used 2671K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 288K, capacity 386K, committed 512K, reserved 1048576K
测试分析:
eden区依次创建了三个2M的对象,占用内存6M,新生代总可用空间为9M,eden区可用空间为8M,由于9M-6M=3M<4M,从而进行GC,发现survivor区空间不足。此时,JVM就启动了内存分配的担保机制,把这6MB的三个对象直接转移到了老年代。此时就把新生代的空间腾出来了,然后把第四个对象(4MB)放入了eden区中。
2、指定Parallel Scavenge+Serial Old作为jvm垃圾回收器
jvm参数将-XX:+UseSerialGC修改为-XX:+UseParallelGC
测试代码如下:
package handlepromotion; public class HandlePromotionFailure { private static final int _1MB = 1024 * 1024; public static void testHandlePromotionFailure() { byte[] allocation1 = new byte[2* _1MB]; byte[] allocation2 = new byte[2* _1MB]; byte[] allocation3 = new byte[2* _1MB]; byte[] allocation4 = new byte[4* _1MB]; } public static void main(String[] args) { testHandlePromotionFailure(); } }
测试结果:
Heap
PSYoungGen total 9216K, used 7292K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 89% used [0x00000000ff600000,0x00000000ffd1f050,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
ParOldGen total 10240K, used 4096K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 40% used [0x00000000fec00000,0x00000000ff000010,0x00000000ff600000)
Metaspace used 2670K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 288K, capacity 386K, committed 512K, reserved 1048576K
接下来我们将第四个对象由4M改为3M,测试结果如下:
[GC (Allocation Failure) [PSYoungGen: 7127K->632K(9216K)] 7127K->6784K(19456K), 0.0053005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [Full GC (Ergonomics) [PSYoungGen: 632K->0K(9216K)] [ParOldGen: 6152K->6669K(10240K)] 6784K->6669K(19456K), [Metaspace: 2664K->2664K(1056768K)], 0.0082392 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] Heap PSYoungGen total 9216K, used 3154K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 38% used [0x00000000ff600000,0x00000000ff914930,0x00000000ffe00000) from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) ParOldGen total 10240K, used 6669K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 65% used [0x00000000fec00000,0x00000000ff283538,0x00000000ff600000) Metaspace used 2671K, capacity 4486K, committed 4864K, reserved 1056768K class space used 288K, capacity 386K, committed 512K, reserved 1048576K
测试分析:
通过和上面的测试结果对比,我们发现当第四个对象无法放入eden区时,此时不会直接进行GC,而是判断如果要分配的内存>=eden区的一半,那么会直接把要分配的内存放入老年代中。否则进行GC操作,如果survivor区空间不足,则把eden区中的6M的对象直接放入老年代区中,eden区新创建4M的对象。
那么为什么3M的时候出现了FullGC呢,原作者也给出了他的答案(https://cloud.tencent.com/developer/article/1082687)。
大概的意思是这种情况下偶尔会发生FullGC,至于原因大概是在使用Parallel Scavenge收集器时,jvm会进行预测,如果晋升到老年代的平均大小大于老年代剩余大小,则认为需要一次FullGC。当虚拟机估算出下次分配可能会发生无法分配的问题时,则会提前发生一次FullGC。
那么针对上面的例子,当gc操作把eden区的6M全部晋升到老年代区中,此时老年代区的大小为10M-6M=4M,jvm预测下一次晋升时老年代空间可能不足,于是提前进行了一次FullGC。
简单验证下,修改jvm参数为-Xms24M -Xmx24M -Xmn10M,将heap内存加大,新生代不变,此时老年代大小为14M。
此时测试结果如下,未发生FullGC:
[GC (Allocation Failure) [PSYoungGen: 7127K->632K(9216K)] 7127K->6784K(23552K), 0.0043651 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap PSYoungGen total 9216K, used 3786K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 38% used [0x00000000ff600000,0x00000000ff914930,0x00000000ffe00000) from space 1024K, 61% used [0x00000000ffe00000,0x00000000ffe9e030,0x00000000fff00000) to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) ParOldGen total 14336K, used 6152K [0x00000000fe800000, 0x00000000ff600000, 0x00000000ff600000) object space 14336K, 42% used [0x00000000fe800000,0x00000000fee02030,0x00000000ff600000) Metaspace used 2671K, capacity 4486K, committed 4864K, reserved 1056768K class space used 288K, capacity 386K, committed 512K, reserved 1048576K