JVM参数设置、分析
内存分配策略
- 优先分配到Eden区
- 大对象直接分配到老年代
- -XX:PretenureSizeThreshold 设定大对象内存大小阈值
- 一般认为大字符串,数组,为大对象
- 因为新生代频繁发生垃圾回收,且采用复制算法,若是 频繁复制大对象,影响效率。
- 长期存活的对象分配到老年代
- 空间分配担保
- 动态对象年龄判断
-verbose:gc -XX:+PrintGCDetails
由如下信息可判断,垃圾收集器新生代肯定使用的是Parellel,老年代可能是GMS
[GC (System.gc()) [PSYoungGen: 693K->464K(6144K)] 693K->472K(19968K), 0.0035897 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 464K->0K(6144K)] [ParOldGen: 8K->329K(13824K)] 472K->329K(19968K), [Metaspace: 2776K->2776K(1056768K)], 0.0042550 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] Heap PSYoungGen total 6144K, used 56K [0x00000007bf980000, 0x00000007c0000000, 0x00000007c0000000) eden space 5632K, 1% used [0x00000007bf980000,0x00000007bf98e2b8,0x00000007bff00000) from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000) to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) ParOldGen total 13824K, used 329K [0x00000007bec00000, 0x00000007bf980000, 0x00000007bf980000) object space 13824K, 2% used [0x00000007bec00000,0x00000007bec52408,0x00000007bf980000) Metaspace used 2782K, capacity 4486K, committed 4864K, reserved 1056768K class space used 293K, capacity 386K, committed 512K, reserved 1048576K
-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC
0 [Full GC (System.gc()) [Tenured: 0K->329K(13696K), 0.0027315 secs] 787K->329K(19840K), [Metaspace: 2777K->2777K(1056768K)], 0.0027869 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] Heap def new generation total 6144K, used 55K [0x00000007bec00000, 0x00000007bf2a0000, 0x00000007bf2a0000) (serial 垃圾收集器的新生代的名称) eden space 5504K, 1% used [0x00000007bec00000, 0x00000007bec0dda0, 0x00000007bf160000) from space 640K, 0% used [0x00000007bf160000, 0x00000007bf160000, 0x00000007bf200000) to space 640K, 0% used [0x00000007bf200000, 0x00000007bf200000, 0x00000007bf2a0000) tenured generation total 13696K, used 329K [0x00000007bf2a0000, 0x00000007c0000000, 0x00000007c0000000) the space 13696K, 2% used [0x00000007bf2a0000, 0x00000007bf2f2408, 0x00000007bf2f2600, 0x00000007c0000000) Metaspace used 2783K, capacity 4486K, committed 4864K, reserved 1056768K class space used 293K, capacity 386K, committed 512K, reserved 1048576K
由此可见jdk1.8默认使用的是Parellel,但是并不是任何环境下都要使用Parellel。应该根据JDK所处的一个环境指定。如果环境是一个服务的server,那么默认指定为Parellel,如果是客户端,收集的内存比较小,停顿时间可观,性能高的情况下,我们一般情况下会使用Serial收集器。
guchunchaodeMacBook-Air:workspaces guchunchao$ java -version java version "1.8.0_181" Java(TM) SE Runtime Environment (build 1.8.0_181-b13) Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
检测内存大于2个G,而且是多核环境,那么默认就认为是Server端。
-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
-Xms 初始堆大小
-Xmx 最大堆大小
-Xmn 年轻代大小
-XX:SurvivorRatio:Eden / Survivor 区的大小比值
public class TestHeap { public static final int M = 1024 * 1024; public static void main(String[] args) { for(int i = 0; i < 7; i++) { byte[] b = new byte[10 * M]; } } }
结果:
[GC (Allocation Failure) [DefNew: 859K->308K(9216K), 0.0012388 secs][Tenured: 0K->307K(10240K), 0.0021830 secs] 859K->307K(19456K), [Metaspace: 2647K->2647K(1056768K)], 0.0034872 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [Tenured(老年代): 307K->295K(10240K)(10M), 0.0016707 secs] 307K->295K(19456K), [Metaspace: 2647K->2647K(1056768K)], 0.0017284 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at com.chaoyijuechen.easypoi.TestHeap.main(TestHeap.java:9) Heap def new generation total 9216K, used 246K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) eden space 8192K, 3% used [0x00000007bec00000, 0x00000007bec3d890, 0x00000007bf400000) from space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000) to space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000) tenured generation total 10240K, used 295K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000) the space 10240K, 2% used [0x00000007bf600000, 0x00000007bf649eb8, 0x00000007bf64a000, 0x00000007c0000000) Metaspace used 2679K, capacity 4486K, committed 4864K, reserved 1056768K class space used 289K, capacity 386K, committed 512K, reserved 1048576K
最大堆内存大小才20M,新生代10M,老年代10M,而类中运行for体内的的对象为10M被视为大对象(已经等于新生代的大小了,不能可丁可卯)所以扔到老年代而老年代10M也放不下,因而发生了OutOfMemory.若是Eden区内存设置为:13M时,这是新生代就能放下了,每放一个发生一次垃圾回收,要不然没有剩余空间放。
-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn13M
[GC (Allocation Failure) [DefNew: 884K->308K(12032K), 0.0028284 secs] 884K->308K(19200K), 0.0031123 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 10548K->306K(12032K), 0.0088254 secs] 10548K->306K(19200K), 0.0088818 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [DefNew: 10762K->306K(12032K), 0.0006648 secs] 10762K->306K(19200K), 0.0007101 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 10546K->306K(12032K), 0.0006589 secs] 10546K->306K(19200K), 0.0006899 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 10546K->306K(12032K), 0.0010204 secs] 10546K->306K(19200K), 0.0010774 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 10546K->306K(12032K), 0.0005397 secs] 10546K->306K(19200K), 0.0005737 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [DefNew: 10546K->306K(12032K), 0.0006790 secs] 10546K->306K(19200K), 0.0007142 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 12032K, used 10762K [0x00000007bec00000, 0x00000007bf900000, 0x00000007bf900000) eden space 10752K, 97% used [0x00000007bec00000, 0x00000007bf635db0, 0x00000007bf680000) from space 1280K, 23% used [0x00000007bf7c0000, 0x00000007bf80cb30, 0x00000007bf900000) to space 1280K, 0% used [0x00000007bf680000, 0x00000007bf680000, 0x00000007bf7c0000) tenured generation total 7168K, used 0K [0x00000007bf900000, 0x00000007c0000000, 0x00000007c0000000) the space 7168K, 0% used [0x00000007bf900000, 0x00000007bf900000, 0x00000007bf900200, 0x00000007c0000000) Metaspace used 2653K, capacity 4486K, committed 4864K, reserved 1056768K class space used 286K, capacity 386K, committed 512K, reserved 1048576K
针对10M的对象共发生了6次新生代的垃圾回收,最后一次for循环产生的对象留在了Eden区,没有其他对象与其竞争堆空间,所以没有发生第7次GC
为什么年轻代的值由10 -> 13后就没有发生OutOfMemory呢?因为此时Eden区能放得下了,而10M时新生代放不下,只能当成大对象扔到老年代,老年代也放不下,所以就报了OutOfMemory异常。
-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
byte[] b = new byte[8 * M]; 发生6次GC ,tenured generation(老年代),the space 10240K, 82% used
byte[] b = new byte[9 * M]; 发生6次GC ,tenured generation(老年代),the space 10240K, 92% used (此时一个对象的空间大小以及大于)
byte[] b = new byte[10 * M]; 对象被视为大对象扔到老年代,老年代也放不下,所以报OutOfMemory异常
-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 Eden / survivor = 8
public class TestHeap { public static final int M = 1024 * 1024; public static void main(String[] args) { byte[] a = new byte[2 * M]; byte[] b = new byte[2 * M]; byte[] c = new byte[2 * M]; byte[] d = new byte[4 * M]; } }
结果:
[GC (Allocation Failure) [DefNew: 7003K->308K(9216K), 0.0142125 secs] 7003K->6452K(19456K), 0.0143851 secs] [Times: user=0.01 sys=0.01, real=0.02 secs] Heap def new generation total 9216K, used 4487K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) eden space 8192K, 51% used [0x00000007bec00000, 0x00000007bf014930, 0x00000007bf400000) from space 1024K, 30% used [0x00000007bf500000, 0x00000007bf54d2d8, 0x00000007bf600000) to space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000) tenured generation total 10240K, used 6144K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000) the space 10240K, 60% used [0x00000007bf600000, 0x00000007bfc00030, 0x00000007bfc00200, 0x00000007c0000000) Metaspace used 2654K, capacity 4486K, committed 4864K, reserved 1056768K class space used 286K, capacity 386K, committed 512K, reserved 1048576K
设置Eden区和survivor区的比值,并手动回收老年代:System.gc()
-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
public class TestHeap { public static final int M = 1024 * 1024; public static void main(String[] args) { byte[] a = new byte[2 * M]; byte[] b = new byte[2 * M]; byte[] c = new byte[2 * M]; byte[] d = new byte[4 * M]; System.gc(); } }
结果:
[GC (Allocation Failure) [DefNew: 7003K->308K(9216K)新生代内存被回收, 0.0131855 secs] 7003K->6452K(19456K), 0.0133545 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] [Full GC (System.gc()) [Tenured: 6144K->6144K(10240K), 0.0021979 secs] 10708K->10546K(19456K), [Metaspace: 2647K->2647K(1056768K)], 0.0022415 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] Heap def new generation total 9216K, used 4730K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) eden space 8192K, 57% used [0x00000007bec00000, 0x00000007bf09e8e0, 0x00000007bf400000) 大概是4M ----> 对象d from space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000) 因为System.gc(),所以这部分被回收掉了,变成了0% to space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000) tenured generation total 10240K, used 6144K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000) the space 10240K, 60% used [0x00000007bf600000, 0x00000007bfc00030, 0x00000007bfc00200, 0x00000007c0000000) 大概是6M ---> 对象a,b,c Metaspace used 2654K, capacity 4486K, committed 4864K, reserved 1056768K class space used 286K, capacity 386K, committed 512K, reserved 1048576K
证明:System.gc(); 垃圾回收时,将from space 所指的区域给回收了。
1、[GC (Allocation Failure):新生代
新生代总共有10M,Eden区8M,survivor1:1M,suvivor2:1M. 对象a,b,c(都为2M)被扔到Eden区,8M占了6M,此时Eden还剩2M,当将d(4M)往Eden区扔的时候,发现Eden区不够了,此时VM就自动发生了一次垃圾收集:
[GC (Allocaltion Failure)。这种GC也叫MannerGC ,这是发生在新生代的GC,新生代对象是朝生夕死,存活的不多,是垃圾收集器重点光顾的地区。所以这种GC操作经常发生。特点是相较于Full GC执行时间短。
2、[Full GC (System.gc()) :老年代
1)、手动发生:System.gc();
2)、系统自动发生
3)、调用频率比gc小得多。老年代的内存中的大对象存活几率非常长,所以Full GC 发生的频率不大。
4)、执行的时间比较长,耗费的性能可能是GC的10倍以上。
非大对象被扔进Eden区
public class TestHeap { public static final int M = 1024 * 1024; public static void main(String[] args) { byte[] b = new byte[10 * M]; } }
vm参数:
-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC
Heap def new generation total 39296K, used 12337K [0x0000000740000000, 0x0000000742aa0000, 0x000000076aaa0000) eden space 34944K, 35% used [0x0000000740000000, 0x0000000740c0c430, 0x0000000742220000) from space 4352K, 0% used [0x0000000742220000, 0x0000000742220000, 0x0000000742660000) to space 4352K, 0% used [0x0000000742660000, 0x0000000742660000, 0x0000000742aa0000) tenured generation total 87424K, used 0K [0x000000076aaa0000, 0x0000000770000000, 0x00000007c0000000) the space 87424K, 0% used [0x000000076aaa0000, 0x000000076aaa0000, 0x000000076aaa0200, 0x0000000770000000) Metaspace used 2653K, capacity 4486K, committed 4864K, reserved 1056768K class space used 286K, capacity 386K, committed 512K, reserved 1048576K
10M 大小的对象 并不被视为大对象,只在Eden区,不直接进老年代
设置大对象阈值
-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -XX:PretenureSizeThreshold=10M
Heap def new generation total 39296K, used 2097K [0x0000000740000000, 0x0000000742aa0000, 0x000000076aaa0000) eden space 34944K, 6% used [0x0000000740000000, 0x000000074020c420, 0x0000000742220000) from space 4352K, 0% used [0x0000000742220000, 0x0000000742220000, 0x0000000742660000) to space 4352K, 0% used [0x0000000742660000, 0x0000000742660000, 0x0000000742aa0000) tenured generation total 87424K, used 10240K [0x000000076aaa0000, 0x0000000770000000, 0x00000007c0000000) the space 87424K, 11% used [0x000000076aaa0000, 0x000000076b4a0010, 0x000000076b4a0200, 0x0000000770000000) Metaspace used 2653K, capacity 4486K, committed 4864K, reserved 1056768K class space used 286K, capacity 386K, committed 512K, reserved 1048576K
因为设置了大对象阈值为10M,所以再次跑程序,被扔进了老年代中
长期存活的对象分配到老年代
-XX:MaxTenuringThreshold 默认是15
每发生一次GC,对象若是没被清理,对象便从Eden到Suvivor0,再在Survivor0和Survivor1之间来回复制。每发生一次位移,Age就+1。直至Age = 15时依然没有被回收掉,便被扔到老年代。
查看策略:建立一个小对象,手动垃圾回收,看回收多少次后对象被扔到老年代。
(“注意:1.6之前OK,1.7&1.8可能age到 2,3次就被扔到老年代了”)待验证
调用一次System.gc()便被扔到了老年代。 ????? 这个参数有待考究。
空间分配担保
-XX:+HandlePromotionFailure
逃逸分析与栈上分配
逃逸分析:分析对象的作用阈
如果一个对象被定义在方法体内部后,那么他的受访问权限仅限于方法体内,一旦其引用外部成员后,那么这个对象就发送了逃逸。
如果这个对象仅仅在方法体内部有效,就认为没有逃逸,就可以把这个对象分配到栈上;否则不分配到栈上。
public class TestAllocation { public TestAllocation obj; /**方法返回TestAllocation对象,发送逃逸*/ public TestAllocation getInstance() { return obj == null ? new TestAllocation() : obj; } /**为成员属性赋值,发生逃逸*/ public void setObj(TestAllocation obj) { this.obj = new TestAllocation(); } /**对象仅仅在本方法中使用,没有发生逃逸*/ public void useObject() { TestAllocation obj = new TestAllocation(); } /**引用成员变量的值,发生逃逸*/ public void useObject2() { TestAllocation obj = getInstance(); } }