JVM23——GC分析
下面我们通过实例对GC的过程分析。开始GC分析之前,先了解一些GC常用的一些参数。其中上表中的晋升是指新生代晋升到老年代。
参考下面代码,设置参数并运行。其中参数-XX:+UserSerialGC
是将垃圾回收器设置为UserSerialGC,这种垃圾回收器的幸存区不会进行自动调整,有助于我们观察现象。
/** * 演示内存的分配策略 */ public class Demo2_1 { private static final int _512KB = 512 * 1024; private static final int _1MB = 1024 * 1024; private static final int _6MB = 6 * 1024 * 1024; private static final int _7MB = 7 * 1024 * 1024; private static final int _8MB = 8 * 1024 * 1024; // -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC public static void main(String[] args) throws InterruptedException { } }
打印信息如下。
Heap def new generation total 9216K, used 2311K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 28% used [0x00000000fec00000, 0x00000000fee41d50, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000) Metaspace used 3268K, capacity 4496K, committed 4864K, reserved 1056768K class space used 347K, capacity 388K, committed 512K, reserved 1048576K
观察到我们分配的新生代内存是10M,但是打印的只有9M,这是因为伊甸园占用8M,幸存区From和To各占用1M,JVM认为幸存区中的内存始终有一块空间是需要空着的,不能存放内容,所以这部分空间没有被计算进来。
新生代的伊甸园只有8M内存,其中28%还已经被占用了,新增以下代码。
ArrayList<byte[]> list = new ArrayList<>(); list.add(new byte[_7MB]);
果然触发了Minor GC。垃圾回收前新生代占用2147k,垃圾回收后占用749k,新生代总大小9216K。堆空间回收前占用2147K,垃圾回收后占用749K,总大小19456K。由于数组被放入了list集合中,而list集合被根GC Root所访问,不会被垃圾回收,所以byte[]数组被移到了幸存区中。垃圾回收后放入了7M的对象。伊甸园占用率93%。
[GC (Allocation Failure) [DefNew: 2147K->749K(9216K), 0.0128891 secs] 2147K->749K(19456K), 0.0129487 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] Heap def new generation total 9216K, used 8327K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 92% used [0x00000000fec00000, 0x00000000ff366830, 0x00000000ff400000) from space 1024K, 73% used [0x00000000ff500000, 0x00000000ff5bb4d8, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
再新增以下代码,创建一个1M大小的数组。
list.add(new byte[_1MB]);
打印信息如下。触发了两次GC操作,在第二次GC操作时,幸存区已经无法容纳这个1M的byte[]对象了,因此部分对象从幸存区晋升到了老年代中。
[GC (Allocation Failure) [DefNew: 2147K->748K(9216K), 0.0039741 secs] 2147K->748K(19456K), 0.0040840 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [DefNew: 8244K->26K(9216K), 0.0096121 secs] 8244K->7932K(19456K), 0.0096617 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] Heap def new generation total 9216K, used 1216K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 14% used [0x00000000fec00000, 0x00000000fed29758, 0x00000000ff400000) from space 1024K, 2% used [0x00000000ff400000, 0x00000000ff406bb8, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 7905K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 77% used [0x00000000ff600000, 0x00000000ffdb8508, 0x00000000ffdb8600, 0x0000000100000000) Metaspace used 3314K, capacity 4496K, committed 4864K, reserved 1056768K class space used 353K, capacity 388K, committed 512K, reserved 1048576K
下面介绍一种大对象直接晋升老年代的情况。将之前的代码注释,直接在list集合中添加8M的byte[]数组。
list.add(new byte[_8MB]);
这种情况伊甸园肯定放不下这个数组,幸存区也放不下,JVM经过计算,发现即使触发了垃圾回收也无法在新生代存放这个对象,这种情况不会触发垃圾回收,如果老年代空间足够这个大对象就会直接晋升老年代。
Heap def new generation total 9216K, used 2478K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 30% used [0x00000000fec00000, 0x00000000fee6bbe8, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 80% used [0x00000000ff600000, 0x00000000ffe00010, 0x00000000ffe00200, 0x0000000100000000) Metaspace used 3333K, capacity 4496K, committed 4864K, reserved 1056768K class space used 357K, capacity 388K, committed 512K, reserved 1048576K
如果新生代,老年代都不足以存放了,就会Out of Memory。
思考一个问题。如果一个非主线程的其他线程发生内存溢出,会导致整个java进程退出吗?实验下。
public static void main(String[] args) throws InterruptedException { new Thread(() -> { ArrayList<byte[]> list = new ArrayList<>(); list.add(new byte[_8MB]); list.add(new byte[_8MB]); }).start(); System.out.println("sleep...."); Thread.sleep(1000L); System.out.println("I'm alive,Haha"); }
结果如下。一个非主线程的其他线程发生内存溢出,不会导致整个java进程退出。
sleep.... [GC (Allocation Failure) [DefNew: 4796K->990K(9216K), 0.0038712 secs][Tenured: 8192K->9179K(10240K), 0.0052058 secs] 12988K->9179K(19456K), [Metaspace: 4269K->4269K(1056768K)], 0.0094779 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] [Full GC (Allocation Failure) [Tenured: 9179K->9124K(10240K), 0.0038569 secs] 9179K->9124K(19456K), [Metaspace: 4269K->4269K(1056768K)], 0.0039093 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space at cn.itcast.jvm.t2.Demo2_1.lambda$main$0(Demo2_1.java:20) at cn.itcast.jvm.t2.Demo2_1$$Lambda$1/1023892928.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) I'm alive,Haha Heap def new generation total 9216K, used 349K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 4% used [0x00000000fec00000, 0x00000000fec57530, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 9124K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 89% used [0x00000000ff600000, 0x00000000ffee9060, 0x00000000ffee9200, 0x0000000100000000) Metaspace used 4294K, capacity 4708K, committed 4992K, reserved 1056768K class space used 467K, capacity 528K, committed 640K, reserved 1048576K
java全栈每日必学,不要高估自己一年能做的事,不要低估自己十年能做的事