Java所提倡的自动内存管理可以自动化解决两个问题:
1.给对象分配内存。
2.回收分配给对象的内存。
关于内存的回收,上一篇已经讲过了,这一篇讲一讲内存的分配。
1.对象优先在Eden分配。
大多数情况下,对象在新生代Eden中分配(关于Eden区,上一篇已经大致介绍了),当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
这边所说的对象是指一个个小对象,不是指大对象,大对象的分配虚拟机有处理方法。
1.测试一,简单测试下内存分配策略:
/** * JVMTest * * vm options: * -Xms20M jvm启动时分配内存20M * -Xmx20M jvm运行过程中分配内存最大为20M * -Xmn10M jvm为新生代分配的内存大小 * 整个JVM内存大小 = 年轻代大小 + 年老代大小 + 持久代大小 * -XX:SurvivorRatio=8 Eden : Survivor 为 8 : 1,另外一个Survivor也是 1 * -XX:+PrintGCDetails */ public class Test { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws Exception{ byte[] a = new byte[_1MB]; byte[] b = new byte[_1MB]; byte[] c = new byte[_1MB]; byte[] d = new byte[_1MB]; } }
Heap PSYoungGen total 9216K, used 5593K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 68% used [0x00000000ff600000,0x00000000ffb76768,0x00000000ffe00000) from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) ParOldGen total 10240K, used 0K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000) PSPermGen total 21248K, used 3004K [0x00000000f9a00000, 0x00000000faec0000, 0x00000000fec00000) object space 21248K, 14% used [0x00000000f9a00000,0x00000000f9cef018,0x00000000faec0000) Process finished with exit code 0
先做个简单的测试,
1.PSYoungGen 是新生代,9216K(total) = 8192k(eden space)+1024k(from space),注意from和to只能同时使用一个内存,两者之间是使用复制算法的。
其实新生代的总容量是 8192K + 1024K + 1024K 是10240K,就是10M。
2.ParOldGen是老年代,total是10240K,也是10M。
3.PSPermGen是永久代,本篇文章不涉及这一块的研究,本篇主要针对堆得内存溢出。
2.测试二,测试对象分配策略,优先分配在Eden中。
/** * JVMTest * * vm options: * -Xms20M jvm启动时分配内存20M * -Xmx20M jvm运行过程中分配内存最大为20M * -Xmn10M jvm为新生代分配的内存大小 * 整个JVM内存大小 = 年轻代大小 + 年老代大小 + 持久代大小 * -XX:SurvivorRatio=8 Eden : Survivor 为 8 : 1,另外一个Survivor也是 1 * -XX:+PrintGCDetails */ public class Test { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws Exception{ for (int i = 0; i < 10; i++) { byte[] ai = new byte[_1MB]; } } }
[GC [PSYoungGen: 7477K->712K(9216K)] 7477K->712K(19456K), 0.0015093 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap PSYoungGen total 9216K, used 5396K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 57% used [0x00000000ff600000,0x00000000ffa93058,0x00000000ffe00000) from space 1024K, 69% used [0x00000000ffe00000,0x00000000ffeb2020,0x00000000fff00000) to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) ParOldGen total 10240K, used 0K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000) PSPermGen total 21248K, used 3075K [0x00000000f9a00000, 0x00000000faec0000, 0x00000000fec00000) object space 21248K, 14% used [0x00000000f9a00000,0x00000000f9d00f30,0x00000000faec0000) Process finished with exit code 0测试二可以明显看出发生了一次Minor GC:
一。GC日志的简单分析:
[GC [PSYoungGen: 7477K->712K(9216K)] 7477K->712K(19456K), 0.0015093 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]简单解释一下上面的GC日志吧:
1.[GC : 只出现了GC,说明是Minor GC,不是Full GC,发生于新生代,Full GC只会发生在老年代。同时代表了垃圾收集的停顿类型。
2.[PSYoungGen : GC收集器的名称。本机测试时默认是打开的Parallel Scavenge收集器。。。。
如果收集器是 Serial,新生代的名称是Default New Generation,显示为[DefNew。
如果收集器是 ParNew。新生代的名称就是 [ParNew。
如果收集器是 Parallel Scavenge收集器,那配套的新生代称为[PSYoungGen。
3. 7477K->712K(9216K)] 7477K->712K(19456K)
3.1 7477K->712K(9216K):“GC前该内存区域已使用容量---> GC后该内存区域已使用容量(该内存区域总容量)”。
3.2 7477K->712K(19456K):此段在方括号外,表示“GC前Java堆已使用容量-->GC后Java堆已使用容量(Java堆总容量)”。
4. 0.0015093 secs
该内存区域GC所占用的时间,单位是秒。有些JVM会给出具体的时间。
5. [Times: user=0.00 sys=0.00, real=0.00 secs]
user:用户态消耗的CPU时间。
sys:内核态消耗的CPU事件。
real:操作从开始到结束的时间。
二。具体的内存分配布局:
PSYoungGen total 9216K, used 5396K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 57% used [0x00000000ff600000,0x00000000ffa93058,0x00000000ffe00000) from space 1024K, 69% used [0x00000000ffe00000,0x00000000ffeb2020,0x00000000fff00000) to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)这边明显看见from内部已经有数据了 ,69%used,这说明在这一次分配之前发生了Minor GC,将Eden区域内的数据复制到from区域。
测试三,(打开UseSerialGC收集器,因为本机测试时默认打开的Parallel 收集器,因此需要手动打开。)
/** *-Xms20M *-Xmx20M *-Xmn10M *-XX:SurvivorRatio=8 *-XX:+PrintGCDetails *-XX:+PrintHeapAtGC *-XX:+UseSerialGC */ public class Test { // private static final int _1MB = 1024 *1024; public static void main(String[] args)throws Exception { // byte[] a = new byte[2* _1MB]; // byte[] b = new byte[2* _1MB]; // byte[] c = new byte[2* _1MB]; // byte[] d = new byte[4* _1MB]; } }先测试下什么都没有的情况,作为起始比较: Eden大概使用了 2012K,永久代里面使用了 3270K
Heap def new generation total 9216K, used 2012K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 24% used [0x00000000f9a00000, 0x00000000f9bf7188, 0x00000000fa200000) from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) tenured generation total 10240K, used 0K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 0% used [0x00000000fa400000, 0x00000000fa400000, 0x00000000fa400200, 0x00000000fae00000) compacting perm gen total 21248K, used 3270K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 15% used [0x00000000fae00000, 0x00000000fb131b18, 0x00000000fb131c00, 0x00000000fc2c0000) No shared spaces configured. Process finished with exit code 0放开注释,使用Serial收集器作为测试,因为本机是默认开启的是Parallel Scavenge收集器,所以需要手动设置一下
/** *-Xms20M *-Xmx20M *-Xmn10M *-XX:SurvivorRatio=8 *-XX:+PrintGCDetails *-XX:+PrintHeapAtGC *-XX:+UseSerialGC */ public class Test { private static final int _1MB = 1024 *1024; public static void main(String[] args)throws Exception { byte[] a = new byte[2* _1MB]; //1 byte[] b = new byte[2* _1MB]; //2 byte[] c = new byte[2* _1MB]; //3 byte[] d = new byte[4* _1MB]; //4 GC } }返回结果需要注意下面红线加粗的值:
AAA : AAA是程序执行到步骤4之前堆中的使用情况,稍微分析一下 used 7826K ,步骤1,2,3分配了总共6MB的空间,大小为6144K,剩下大概1682K,大概和上面的初始大小相差不大,个人认为是一些其他东西也跟随着分配了。但是说实话我不清楚是些什么东西。
BBB: BBB是在执行步骤4之前发现Eden区空间不足了,于是发生了一次Minor GC,注意下这边收回的大小,7826K->635K(9216K),基本上都回收了(回收了a,b,c对象的大小),只剩下635K没有回收,可以猜想635K的大小是程序运行起来分配的东西,至于是什么,我也不清楚。7826K->6779K(19456K) 这个值是关键,
6779K代表的是什么?上面也说过,代表的是GC回收之后的堆使用大小,说明虽然执行了回收,但是新生代已经没有空间用于存放这三个对象了,Survivor的空间只有1MB,也没有空间存放,所以只能通过分配担保机制存放到年老代里面。具体可见DDD区域存在了大小为60%的已使用空间。
CCC:Survivor区域现在存放了什么?书中这一块是 0,但是我执行下来这边依然存放了62%的空间,大概就是上面执行Monir GC 后剩余的大小 635K(个人纯猜测,因为我使用的是Server模式运行Serial模式,而书中作者是运行的Clinet模式,其次有可能也是JVM的版本不同,我也不清楚怎么去验证,网上大多是直接复制书中的代码片段和打印片段,也没有谁具体说下这边。)
DDD:DDD区域就是年老代了,就是存放了a,b,c三个对象。
EEE:EEE是执行了GC和重新分配之后的Heap情况,现在变成56%,就只存在了对象d(4MB),剩下的内存分配了啥不知道。。。。
{Heap before GC invocations=0 (full 0): def new generation total 9216K, used 7826K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 95% used [0x00000000f9a00000, 0x00000000fa1a4a90, 0x00000000fa200000) AAA from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) tenured generation total 10240K, used 0K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 0% used [0x00000000fa400000, 0x00000000fa400000, 0x00000000fa400200, 0x00000000fae00000) compacting perm gen total 21248K, used 2971K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 13% used [0x00000000fae00000, 0x00000000fb0e6f68, 0x00000000fb0e7000, 0x00000000fc2c0000) No shared spaces configured. [GC [DefNew: 7826K->635K(9216K), 0.0046670 secs] 7826K->6779K(19456K), 0.0046907 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] BBB Heap after GC invocations=1 (full 0): def new generation total 9216K, used 635K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 0% used [0x00000000f9a00000, 0x00000000f9a00000, 0x00000000fa200000) from space 1024K, 62% used [0x00000000fa300000, 0x00000000fa39ef68, 0x00000000fa400000) CCC 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) DDD compacting perm gen total 21248K, used 2971K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 13% used [0x00000000fae00000, 0x00000000fb0e6f68, 0x00000000fb0e7000, 0x00000000fc2c0000) No shared spaces configured. } Heap def new generation total 9216K, used 5225K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 56% used [0x00000000f9a00000, 0x00000000f9e7b5f0, 0x00000000fa200000) EEE from space 1024K, 62% used [0x00000000fa300000, 0x00000000fa39ef68, 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 2982K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 14% used [0x00000000fae00000, 0x00000000fb0e9920, 0x00000000fb0e9a00, 0x00000000fc2c0000) No shared spaces configured. Process finished with exit code 0
测试四,(打开UseSerialGC收集器,因为本机测试时默认打开的Parallel 收集器,因此需要手动打开),测试小对象的联系分配情况。
/** *-Xms20M *-Xmx20M *-Xmn10M *-XX:SurvivorRatio=8 *-XX:+PrintGCDetails *-XX:+PrintHeapAtGC *-XX:+UseSerialGC */ public class Test { private static final int _1MB = 1024 *1024; public static void main(String[] args)throws Exception { byte[] a1 = new byte[_1MB]; byte[] a2 = new byte[_1MB]; byte[] a3 = new byte[_1MB]; System.out.println("a3 over#################"); byte[] a4 = new byte[_1MB]; System.out.println("a4 over#################"); byte[] a5 = new byte[_1MB]; System.out.println("a5 over#################"); byte[] a6 = new byte[_1MB]; System.out.println("a6 over#################"); byte[] a7 = new byte[_1MB]; System.out.println("a7 over#################"); byte[] a8 = new byte[_1MB]; System.out.println("a8 over#################"); byte[] a9 = new byte[_1MB]; System.out.println("a9 over#################"); byte[] a10 = new byte[_1MB]; System.out.println("a10 over#################"); byte[] a11 = new byte[_1MB]; System.out.println("a11 over##################"); byte[] a12 = new byte[_1MB]; System.out.println("a12 over##################"); byte[] a13 = new byte[_1MB]; System.out.println("a13 over##################"); byte[] a14 = new byte[_1MB]; System.out.println("a14 over##################"); byte[] a15 = new byte[_1MB]; } }
连续分配15个对象,因为分配16个时已经堆溢出了,所以只测试这种情况:
下面的情况上面已经分析过,我关注点在第14次对象分配时的回收情况,因为我指定了垃圾收集器是Serial收集器,因此显示的是DefNew,可以看见对于老年代的使用,其实并没有回收掉,内存占用从6144K--> 9217K了,永久代也没有进行回收,新生代也没回收,因为都是存活对象,回收不了。
[GC [DefNew: 8266K->8266K(9216K), 0.0000090 secs][Tenured: 6144K->9217K(10240K), 0.0054906 secs] 14410K->13965K(19456K), [Perm : 3267K->3267K(21248K)], 0.0055330 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap after GC invocations=2 (full 1):
a3 over################# a4 over################# a5 over################# a6 over################# {Heap before GC invocations=0 (full 0): def new generation total 9216K, used 8006K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 97% used [0x00000000f9a00000, 0x00000000fa1d1a60, 0x00000000fa200000) from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) tenured generation total 10240K, used 0K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 0% used [0x00000000fa400000, 0x00000000fa400000, 0x00000000fa400200, 0x00000000fae00000) compacting perm gen total 21248K, used 3264K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 15% used [0x00000000fae00000, 0x00000000fb130150, 0x00000000fb130200, 0x00000000fc2c0000) No shared spaces configured. [GC [DefNew: 8006K->673K(9216K), 0.0044229 secs] 8006K->6817K(19456K), 0.0044463 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap after GC invocations=1 (full 0): def new generation total 9216K, used 673K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 0% used [0x00000000f9a00000, 0x00000000f9a00000, 0x00000000fa200000) from space 1024K, 65% used [0x00000000fa300000, 0x00000000fa3a8540, 0x00000000fa400000) to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) tenured generation total 10240K, used 6144K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 60% used [0x00000000fa400000, 0x00000000faa00060, 0x00000000faa00200, 0x00000000fae00000) compacting perm gen total 21248K, used 3264K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 15% used [0x00000000fae00000, 0x00000000fb130150, 0x00000000fb130200, 0x00000000fc2c0000) No shared spaces configured. } a7 over################# a8 over################# a9 over################# a10 over################# a11 over################## a12 over################## a13 over################## {Heap before GC invocations=1 (full 0): def new generation total 9216K, used 8266K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 92% used [0x00000000f9a00000, 0x00000000fa16a3b8, 0x00000000fa200000) from space 1024K, 65% used [0x00000000fa300000, 0x00000000fa3a8540, 0x00000000fa400000) to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) tenured generation total 10240K, used 6144K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 60% used [0x00000000fa400000, 0x00000000faa00060, 0x00000000faa00200, 0x00000000fae00000) compacting perm gen total 21248K, used 3267K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 15% used [0x00000000fae00000, 0x00000000fb130d70, 0x00000000fb130e00, 0x00000000fc2c0000) No shared spaces configured. [GC [DefNew: 8266K->8266K(9216K), 0.0000090 secs][Tenured: 6144K->9217K(10240K), 0.0054906 secs] 14410K->13965K(19456K), [Perm : 3267K->3267K(21248K)], 0.0055330 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap after GC invocations=2 (full 1): def new generation total 9216K, used 4748K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 57% used [0x00000000f9a00000, 0x00000000f9ea33e0, 0x00000000fa200000) from space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) tenured generation total 10240K, used 9217K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 90% used [0x00000000fa400000, 0x00000000fad00418, 0x00000000fad00600, 0x00000000fae00000) compacting perm gen total 21248K, used 3267K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 15% used [0x00000000fae00000, 0x00000000fb130d70, 0x00000000fb130e00, 0x00000000fc2c0000) No shared spaces configured. } a14 over################## Heap def new generation total 9216K, used 6942K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 84% used [0x00000000f9a00000, 0x00000000fa0c7b48, 0x00000000fa200000) from space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) tenured generation total 10240K, used 9217K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 90% used [0x00000000fa400000, 0x00000000fad00418, 0x00000000fad00600, 0x00000000fae00000) compacting perm gen total 21248K, used 3274K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 15% used [0x00000000fae00000, 0x00000000fb132b08, 0x00000000fb132c00, 0x00000000fc2c0000) No shared spaces configured. Process finished with exit code 0
2.大对象直接进入老年代
所谓的大对象,最典型的大对象就是很长的字符串或者上例中的数组,原本大对象的内存分配JVM就很讨厌了,但是最烦的就是那种“朝生夕死”的大对象,这个需要在工作中避免,因为这种大对象最容易触发本身内存还有很多,但是提前触发了GC回收。
测试一:延续上例,采用Serial收集器,需要添加vm options参数,让虚拟机知道大对象的界限
/** *-Xms20M *-Xmx20M *-Xmn10M *-XX:SurvivorRatio=8 *-XX:+PrintGCDetails *-XX:+PrintHeapAtGC *-XX:+UseSerialGC *-XX:PretenureSizeThreshold=3145728 */ public class Test { private static final int _1MB = 1024 *1024; public static void main(String[] args)throws Exception { byte[] a1 = new byte[_1MB]; byte[] a2 = new byte[_1MB]; byte[] a3 = new byte[2*_1MB]; byte[] a4 = new byte[3 * _1MB]; //大对象 byte[] a5 = new byte[4*_1MB]; //大对象 } }
原本如果没有大对象参数的话,会触发Minor GC,因为此时新生代已经发布下了,但是添加了这个参数,使得JVM能够识别大对象,直接分配到老年代中。
Heap def new generation total 9216K, used 6122K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 74% used [0x00000000f9a00000, 0x00000000f9ffa9f0, 0x00000000fa200000) from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) tenured generation total 10240K, used 7168K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 70% used [0x00000000fa400000, 0x00000000fab00020, 0x00000000fab00200, 0x00000000fae00000) compacting perm gen total 21248K, used 3271K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 15% used [0x00000000fae00000, 0x00000000fb131c00, 0x00000000fb131c00, 0x00000000fc2c0000) No shared spaces configured.
对于这个参数-XX:PretenureSizeThreshold,只有Serial和ParNew能识别,Parallel Sevenage并不能识别。
测试二,默认采用Server模式下的回收机制,Parallel Scavenge收集器
/
** *-Xms20M *-Xmx20M *-Xmn10M *-XX:SurvivorRatio=8 *-XX:+PrintGCDetails *-XX:+PrintHeapAtGC */ public class Test { private static final int _1MB = 1024 *1024; public static void main(String[] args)throws Exception { byte[] a3 = new byte[2*_1MB]; byte[] a4 = new byte[2*_1MB]; byte[] a5 = new byte[4*_1MB]; } }
可以看见采用这种垃圾回收的话,不会触发垃圾回收,和Serial设置了大小一样,直接在老年代分配,但是对于连续小对象,大家其实都一样,都会触发垃圾回收。
Heap PSYoungGen total 9216K, used 6122K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 74% used [0x00000000ff600000,0x00000000ffbfa9e0,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) PSPermGen total 21248K, used 3270K [0x00000000f9a00000, 0x00000000faec0000, 0x00000000fec00000) object space 21248K, 15% used [0x00000000f9a00000,0x00000000f9d31bc0,0x00000000faec0000) Process finished with exit code 0
3.长期存活的对象将进入老年代
JVM采用了分代收集的思想管理内存,因此内存回收时就需要能识别出哪些对象应该放在新声代,哪些需要放在老年代,所以JVM为每个对象定义了一个对象年龄(Age)计数器。
1.如果对象在Eden出生并且经过第一次Minor GC后任然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且年龄设置为1。
2.如果对象在Survivor空间中每熬过一次Minor GC,年龄就增加1,当年龄增加到一定程度(默认为15),就会被晋升到老年代中。
这个值可以设置,-XX:MaxTenuringThreshold
/** * JVMTest * * vm options: * -Xms20M jvm启动时分配内存20M * -Xmx20M jvm运行过程中分配内存最大为20M * -Xmn10M jvm为新生代分配的内存大小 * 整个JVM内存大小 = 年轻代大小 + 年老代大小 + 持久代大小 * -XX:SurvivorRatio=8 Eden : Survivor 为 8 : 1,另外一个Survivor也是 1 * -XX:+PrintGCDetails * -XX:+UseSerialGC 打开Serial和Serial Old 收集器 * -XX:+PrintHeapAtGC * -XX:+PrintTenuringDistribution * -XX:MaxTenuringThreshold=1 */ public class Test { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws Exception{ byte[] a = new byte[_1MB / 4]; //1 System.out.println("a is over###########################"); byte[] b = new byte[4 * _1MB]; //2 System.out.println("b is over######################"); byte[] c = new byte[4 * _1MB]; //3 System.out.println("c is over####################"); byte[] d = new byte[4 * _1MB]; //4 System.out.println("d is over ###################"); } }
测试结果如下:分析到是不难。
AAA:上图测试代码中执行步骤1和步骤2之后,内存分配如下,占用了69%,就是对象a,b。
BBB:执行到步骤3时,执行了一次Minor GC,但是对象a只有256K,因此能够存放于Survivor区域中,但是对象b是4MB,只能存放在老年代了,这边存放于老年代是由于分配担保机制。
CCC:CCC处就是此时内存分配情况。
DDD:DDD处是执行了步骤3,分配了对象c大小为4MB,直接分配在Eden区域。
EEE:此处是进行过Minor GC了,可以看见这边新生代内存变成0KB了,这是因为此处设置了对象的年龄为1,此处在Survivor区域中的已经存活了1次Minor GC了,所以直接去老年代了。
FFF:可见新生代为0KB。
GGG:步骤4分配的对象4直接分配在Eden区域。
a is over########################### b is over###################### {Heap before GC invocations=0 (full 0): def new generation total 9216K, used 5685K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 69% used [0x00000000f9a00000, 0x00000000f9f8d788, 0x00000000fa200000) AAA from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) tenured generation total 10240K, used 0K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 0% used [0x00000000fa400000, 0x00000000fa400000, 0x00000000fa400200, 0x00000000fae00000) compacting perm gen total 21248K, used 2991K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 14% used [0x00000000fae00000, 0x00000000fb0ebd10, 0x00000000fb0ebe00, 0x00000000fc2c0000) No shared spaces configured. [GC [DefNew Desired survivor size 524288 bytes, new threshold 1 (max 1) - age 1: 806816 bytes, 806816 total : 5685K->787K(9216K), 0.0039566 secs] 5685K->4883K(19456K), 0.0039799 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] BBB Heap after GC invocations=1 (full 0): def new generation total 9216K, used 787K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 0% used [0x00000000f9a00000, 0x00000000f9a00000, 0x00000000fa200000) from space 1024K, 76% used [0x00000000fa300000, 0x00000000fa3c4fa0, 0x00000000fa400000) CCC to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) tenured generation total 10240K, used 4096K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 40% used [0x00000000fa400000, 0x00000000fa800010, 0x00000000fa800200, 0x00000000fae00000) compacting perm gen total 21248K, used 2991K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 14% used [0x00000000fae00000, 0x00000000fb0ebd10, 0x00000000fb0ebe00, 0x00000000fc2c0000) No shared spaces configured. } c is over#################### {Heap before GC invocations=1 (full 0): def new generation total 9216K, used 5230K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 54% used [0x00000000f9a00000, 0x00000000f9e56a90, 0x00000000fa200000) from space 1024K, 76% used [0x00000000fa300000, 0x00000000fa3c4fa0, 0x00000000fa400000) to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) tenured generation total 10240K, used 4096K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 40% used [0x00000000fa400000, 0x00000000fa800010, 0x00000000fa800200, 0x00000000fae00000) DDD compacting perm gen total 21248K, used 2999K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 14% used [0x00000000fae00000, 0x00000000fb0edfd0, 0x00000000fb0ee000, 0x00000000fc2c0000) No shared spaces configured. [GC [DefNew Desired survivor size 524288 bytes, new threshold 1 (max 1) - age 1: 296 bytes, 296 total : 5230K->0K(9216K), 0.0026283 secs] 9326K->8980K(19456K), 0.0026459 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] EEE Heap after GC invocations=2 (full 0): def new generation total 9216K, used 0K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 0% used [0x00000000f9a00000, 0x00000000f9a00000, 0x00000000fa200000) from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200128, 0x00000000fa300000) to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) tenured generation total 10240K, used 8979K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 87% used [0x00000000fa400000, 0x00000000facc4f28, 0x00000000facc5000, 0x00000000fae00000) FFF compacting perm gen total 21248K, used 2999K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 14% used [0x00000000fae00000, 0x00000000fb0edfd0, 0x00000000fb0ee000, 0x00000000fc2c0000) No shared spaces configured. } d is over ################### Heap def new generation total 9216K, used 4463K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 54% used [0x00000000f9a00000, 0x00000000f9e5bc88, 0x00000000fa200000) from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200128, 0x00000000fa300000) to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) tenured generation total 10240K, used 8979K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 87% used [0x00000000fa400000, 0x00000000facc4f28, 0x00000000facc5000, 0x00000000fae00000) GGG compacting perm gen total 21248K, used 3063K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 14% used [0x00000000fae00000, 0x00000000fb0fe2b0, 0x00000000fb0fe400, 0x00000000fc2c0000) No shared spaces configured. Process finished with exit code 0
4.动态对象年龄判断
JVM并不是要求对象的年龄必须达到MaxTenuringThreshold设置的值才能晋升到老年代,如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半时,年龄大于或等于改年龄的对象就可以直接进入老年代,无需等到设置的值。(看到这里,我对上一个测试例子中Survivor中的对象也进入老年代的现象才解惑了FFF处的现象)
这里就不做测试了,因为我发现本地测试时并不能出现和书中一致的值,但是现象就和第三点和第四点一致,我复现时,要么不能满足50%,要么就是大于50%的情况,在大于50%的情况下我即使设置了年龄是15,也会复制到老年代中去,蛋疼。
5.空间分配担保
/** *-Xms20M *-Xmx20M *-Xmn10M *-XX:SurvivorRatio=8 *-XX:+PrintGCDetails *-XX:+PrintHeapAtGC */ public class Test { private static final int _1MB = 1024 *1024; public static void main(String[] args)throws Exception { byte[] b1,b2,b3,b4,b5,b6,b7; b1 = new byte[2 * _1MB]; b2 = new byte[2 * _1MB]; System.out.println("##########1111"); b3 = new byte[2 * _1MB]; System.out.println("##########1"); b1 = null; System.out.println("##########2"); b4 = new byte[2 * _1MB]; System.out.println("##########3"); b5 = new byte[2 * _1MB]; System.out.println("##########4"); b6 = new byte[2 * _1MB]; System.out.println("##########5"); b4 = null; System.out.println("##########6"); b5 = null; System.out.println("##########7"); b6 = null; System.out.println("##########8"); b7 = new byte[2*_1MB]; System.out.println("##########9"); } }
##########1111 ##########1 ##########2 {Heap before GC invocations=0 (full 0): def new generation total 9216K, used 8006K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 97% used [0x00000000f9a00000, 0x00000000fa1d1a30, 0x00000000fa200000) from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) tenured generation total 10240K, used 0K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 0% used [0x00000000fa400000, 0x00000000fa400000, 0x00000000fa400200, 0x00000000fae00000) compacting perm gen total 21248K, used 3264K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 15% used [0x00000000fae00000, 0x00000000fb130058, 0x00000000fb130200, 0x00000000fc2c0000) No shared spaces configured. [GC [DefNew: 8006K->673K(9216K), 0.0041063 secs] 8006K->4769K(19456K), 0.0041310 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap after GC invocations=1 (full 0): def new generation total 9216K, used 673K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 0% used [0x00000000f9a00000, 0x00000000f9a00000, 0x00000000fa200000) from space 1024K, 65% used [0x00000000fa300000, 0x00000000fa3a84a8, 0x00000000fa400000) to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) tenured generation total 10240K, used 4096K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 40% used [0x00000000fa400000, 0x00000000fa800020, 0x00000000fa800200, 0x00000000fae00000) compacting perm gen total 21248K, used 3264K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 15% used [0x00000000fae00000, 0x00000000fb130058, 0x00000000fb130200, 0x00000000fc2c0000) No shared spaces configured. } ##########3 ##########4 ##########5 ##########6 ##########7 ##########8 {Heap before GC invocations=1 (full 0): def new generation total 9216K, used 7242K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 80% used [0x00000000f9a00000, 0x00000000fa06a378, 0x00000000fa200000) from space 1024K, 65% used [0x00000000fa300000, 0x00000000fa3a84a8, 0x00000000fa400000) to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) tenured generation total 10240K, used 4096K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 40% used [0x00000000fa400000, 0x00000000fa800020, 0x00000000fa800200, 0x00000000fae00000) compacting perm gen total 21248K, used 3267K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 15% used [0x00000000fae00000, 0x00000000fb130c78, 0x00000000fb130e00, 0x00000000fc2c0000) No shared spaces configured. [GC [DefNew: 7242K->0K(9216K), 0.0014795 secs] 11338K->4749K(19456K), 0.0014972 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap after GC invocations=2 (full 0): def new generation total 9216K, used 0K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 0% used [0x00000000f9a00000, 0x00000000f9a00000, 0x00000000fa200000) from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200280, 0x00000000fa300000) to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) tenured generation total 10240K, used 4748K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 46% used [0x00000000fa400000, 0x00000000fa8a3328, 0x00000000fa8a3400, 0x00000000fae00000) compacting perm gen total 21248K, used 3267K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 15% used [0x00000000fae00000, 0x00000000fb130c78, 0x00000000fb130e00, 0x00000000fc2c0000) No shared spaces configured. } ##########9 Heap def new generation total 9216K, used 2194K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) eden space 8192K, 26% used [0x00000000f9a00000, 0x00000000f9c247d0, 0x00000000fa200000) from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200280, 0x00000000fa300000) to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) tenured generation total 10240K, used 4748K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) the space 10240K, 46% used [0x00000000fa400000, 0x00000000fa8a3328, 0x00000000fa8a3400, 0x00000000fae00000) compacting perm gen total 21248K, used 3274K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) the space 21248K, 15% used [0x00000000fae00000, 0x00000000fb132a10, 0x00000000fb132c00, 0x00000000fc2c0000) No shared spaces configured. Process finished with exit code 0
综上可见:
1. 在对象b4分配时,进行了一次Minor GC,将对象b2,b3分配进了老年代。
2.在对象b7分配时,在Survivor空间不足以放下它时,会根据HandlePromotionFailure这个参数进行一次GC,
在1.6之前,
HandlePromotionFailure 为true时,JVM会冒险进行一次Minor GC来使b7对象得到空间存放在新生代,如果GC后发现空间还是不足,则会进行一次Major GC(Full GC) 。
HandlePromotionFailure 为false时,JVM会进行Full GC。
在1.6 之后,HandlePromotionFailure 一直都是true,只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC。。