对象的内存分配,往大方向讲,就是在堆上分配(但也可能经过JIT编译后被拆分为标量类型并间接地在栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况下也可能会直接分配在老年代中,分配的规则并不是百分百固定的,其细节取决于当前使用的是哪一种垃圾收集器,还有虚拟机中与内存有关的参数设置。
下面是在测试时使用client模式,试验环境jdk 1.6.0_37,验证使用Serial/Serial Old收集器下的内存分配和回收策略。
一、对象优先在Eden分配
- 大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
- Minor GC和Full GC(Major GC)区别:Minor GC是指发生在新生代的垃圾收集动作,因为Java对象大多具备朝生夕灭的特定,所以Minor GC非常频繁,一般回收速度也比较快。Full GC是指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但并非绝对,Parellel Scavenge收集器策略可选择直接进行Major GC的策略),Major GC的速度一般比Minor GC慢10倍以上,所以应该减少触发的次数。
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* 限制java堆大小为20M,年轻代为10M,Eden为8M,两个survivor分别为1M
*/
public static void testAllocation() {
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation4 = new byte[4 * _1MB]; // 出现一次Minor GC
/**
[GC [DefNew: 6487K->152K(9216K), 0.0040116 secs] 6487K->6296K(19456K), 0.0040436 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4576K [0x32750000, 0x33150000, 0x33150000)
eden space 8192K, 54% used [0x32750000, 0x32ba1fa8, 0x32f50000)
from space 1024K, 14% used [0x33050000, 0x33076150, 0x33150000)
to space 1024K, 0% used [0x32f50000, 0x32f50000, 0x33050000)
tenured generation total 10240K, used 6144K [0x33150000, 0x33b50000, 0x33b50000)
the space 10240K, 60% used [0x33150000, 0x33750030, 0x33750200, 0x33b50000)
compacting perm gen total 12288K, used 376K [0x33b50000, 0x34750000, 0x37b50000)
the space 12288K, 3% used [0x33b50000, 0x33bae2c0, 0x33bae400, 0x34750000)
ro space 10240K, 55% used [0x37b50000, 0x380d1140, 0x380d1200, 0x38550000)
rw space 12288K, 55% used [0x38550000, 0x38bf44c8, 0x38bf4600, 0x39150000)
*
*/
}
二、大对象直接进入老年代
- 所谓的大对象是指需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。大对象对虚拟机的内存分配来说就是一个坏消息(程序员应该避免写那种朝生夕灭的短命大对象),经常出现大对象容易导致内存还有不少就提前触发垃圾收集器以获取足够的连续空间来“安置”它们。
- 虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。这样做的目的是避免在Eden去以及两个Survivor区之间发生大量的内存复制。
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728
* 对象超过3M 时直接进入老年代
*/
public static void testPretenureSizeThreshold() {
byte[] allocation;
allocation = new byte[4 * _1MB]; //直接分配在老年代中
/**
Heap
def new generation total 9216K, used 507K [0x32750000, 0x33150000, 0x33150000)
eden space 8192K, 6% used [0x32750000, 0x327cef38, 0x32f50000)
from space 1024K, 0% used [0x32f50000, 0x32f50000, 0x33050000)
to space 1024K, 0% used [0x33050000, 0x33050000, 0x33150000)
tenured generation total 10240K, used 4096K [0x33150000, 0x33b50000, 0x33b50000)
the space 10240K, 40% used [0x33150000, 0x33550010, 0x33550200, 0x33b50000)
compacting perm gen total 12288K, used 376K [0x33b50000, 0x34750000, 0x37b50000)
the space 12288K, 3% used [0x33b50000, 0x33bae3b8, 0x33bae400, 0x34750000)
ro space 10240K, 55% used [0x37b50000, 0x380d1140, 0x380d1200, 0x38550000)
rw space 12288K, 55% used [0x38550000, 0x38bf44c8, 0x38bf4600, 0x39150000)
*/
}
三、长期存活对象进入老年代
- 对象在两个survivor区每复制(Minor gc)一次,年龄就增长一岁,当超过指定最大随时时转移到老年代中。
- -XX:MaxTenuringThreshold=8 参数用于设定对象最大年龄阈值,分别设置为8和1运行的结果是不一样的。
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution
*
*/
@SuppressWarnings("unused")
public static void testTenuringThreshold() {
byte[] allocation1, allocation2, allocation3;
allocation1 = new byte[_1MB / 4]; //262144 什么时候进入老年代决定于XX:MaxTenuringThreshold设置
allocation2 = new byte[4 * _1MB]; //4194304
allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
/**
*
* XX:MaxTenuringThreshold =8
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 8 (max 8)
- age 1: 418144 bytes, 418144 total
: 4695K->408K(9216K), 0.0036693 secs] 4695K->4504K(19456K), 0.0036983 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 8 (max 8)
- age 1: 136 bytes, 136 total
- age 2: 417936 bytes, 418072 total
: 4668K->408K(9216K), 0.0010034 secs] 8764K->4504K(19456K), 0.0010296 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4668K [0x32750000, 0x33150000, 0x33150000)
eden space 8192K, 52% used [0x32750000, 0x32b78fe0, 0x32f50000)
from space 1024K, 39% used [0x32f50000, 0x32fb6118, 0x33050000)
to space 1024K, 0% used [0x33050000, 0x33050000, 0x33150000)
tenured generation total 10240K, used 4096K [0x33150000, 0x33b50000, 0x33b50000)
the space 10240K, 40% used [0x33150000, 0x33550010, 0x33550200, 0x33b50000)
compacting perm gen total 12288K, used 377K [0x33b50000, 0x34750000, 0x37b50000)
the space 12288K, 3% used [0x33b50000, 0x33bae5b8, 0x33bae600, 0x34750000)
ro space 10240K, 55% used [0x37b50000, 0x380d1140, 0x380d1200, 0x38550000)
rw space 12288K, 55% used [0x38550000, 0x38bf44c8, 0x38bf4600, 0x39150000)
*/
/**
*
* XX:MaxTenuringThreshold=1
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 418144 bytes, 418144 total
: 4695K->408K(9216K), 0.0054252 secs] 4695K->4504K(19456K), 0.0054708 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 136 bytes, 136 total
: 4668K->0K(9216K), 0.0013601 secs] 8764K->4504K(19456K), 0.0013867 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4260K [0x32750000, 0x33150000, 0x33150000)
eden space 8192K, 52% used [0x32750000, 0x32b78fe0, 0x32f50000)
from space 1024K, 0% used [0x32f50000, 0x32f50088, 0x33050000)
to space 1024K, 0% used [0x33050000, 0x33050000, 0x33150000)
tenured generation total 10240K, used 4504K [0x33150000, 0x33b50000, 0x33b50000)
the space 10240K, 43% used [0x33150000, 0x335b60a0, 0x335b6200, 0x33b50000)
compacting perm gen total 12288K, used 377K [0x33b50000, 0x34750000, 0x37b50000)
the space 12288K, 3% used [0x33b50000, 0x33bae5c0, 0x33bae600, 0x34750000)
ro space 10240K, 55% used [0x37b50000, 0x380d1140, 0x380d1200, 0x38550000)
rw space 12288K, 55% used [0x38550000, 0x38bf44c8, 0x38bf4600, 0x39150000)
*/
}
四、对象动态年龄判断
- 虽然可以设置对象最大年龄阈值,但有时候虚拟机会根据内存情况自己动态计算对象的阈值。
当survivor区相同年龄的大小不小于survivor区的一半时,虚拟机会把survivor区等于和大于此年龄的对象转移到老年代。
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:+PrintTenuringDistribution
*
*/
@SuppressWarnings("unused")
public static void testTenuringThreshold2() {
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[_1MB / 4]; // allocation1+allocation2大于survivo空间一半
allocation2 = new byte[_1MB / 4];
allocation3 = new byte[4 * _1MB];
allocation4 = new byte[4 * _1MB];
allocation4 = null;
allocation4 = new byte[4 * _1MB];
/**
* [GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age 1: 680304 bytes, 680304 total
: 4951K->664K(9216K), 0.0033210 secs] 4951K->4760K(19456K), 0.0033442 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 136 bytes, 136 total
: 4924K->0K(9216K), 0.0011772 secs] 9020K->4760K(19456K), 0.0011987 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4260K [0x32750000, 0x33150000, 0x33150000)
eden space 8192K, 52% used [0x32750000, 0x32b78fe0, 0x32f50000)
from space 1024K, 0% used [0x32f50000, 0x32f50088, 0x33050000)
to space 1024K, 0% used [0x33050000, 0x33050000, 0x33150000)
tenured generation total 10240K, used 4760K [0x33150000, 0x33b50000, 0x33b50000)
the space 10240K, 46% used [0x33150000, 0x335f60b0, 0x335f6200, 0x33b50000)
compacting perm gen total 12288K, used 377K [0x33b50000, 0x34750000, 0x37b50000)
the space 12288K, 3% used [0x33b50000, 0x33bae5c0, 0x33bae600, 0x34750000)
ro space 10240K, 55% used [0x37b50000, 0x380d1140, 0x380d1200, 0x38550000)
rw space 12288K, 55% used [0x38550000, 0x38bf44c8, 0x38bf4600, 0x39150000)
*/
}
解释:
new threshold 1 (max 15) 表示 虚拟机自己计算的阈值为1 ,最大15。
五、空间分配担保
- 在发送minor gc之前,虚拟机会首先检查老年代最大可连续空间是否大于新生代所有对象总和,如果这个条件成立,可以确保这次minor gc是安全的, 如果不成立,虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可连续空间是否大于历次晋升到老年代对象的评价大小,如果大于,将尝试一次minor gc,尽管这次minor gc是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那么这时也要改为一次Full gc。
- 在jdk1.6 update24之后,HandlePromotionFailure参数不会影响虚拟机空间分配担保策略,虚拟机改为,只要老年代最大连续空间大于新生代对象总和或者大于历次晋升平均大小,都将进行minor gc,否则将进行Full gc。
/**
* VM参数:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure
*/
@SuppressWarnings("unused")
public static void testHandlePromotion() {
byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation1 = null;
allocation4 = new byte[2 * _1MB];
allocation5 = new byte[2 * _1MB];
allocation6 = new byte[2 * _1MB];
allocation4 = null;
allocation5 = null;
allocation6 = null;
allocation7 = new byte[2 * _1MB];
/**
*
java.version = 1.6.0_37
[GC [DefNew: 6487K->152K(9216K), 0.0040346 secs] 6487K->4248K(19456K), 0.0040639 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6546K->152K(9216K), 0.0004896 secs] 10642K->4248K(19456K), 0.0005141 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 2364K [0x32750000, 0x33150000, 0x33150000)
eden space 8192K, 27% used [0x32750000, 0x32978fe0, 0x32f50000)
from space 1024K, 14% used [0x32f50000, 0x32f76108, 0x33050000)
to space 1024K, 0% used [0x33050000, 0x33050000, 0x33150000)
tenured generation total 10240K, used 4096K [0x33150000, 0x33b50000, 0x33b50000)
the space 10240K, 40% used [0x33150000, 0x33550020, 0x33550200, 0x33b50000)
compacting perm gen total 12288K, used 377K [0x33b50000, 0x34750000, 0x37b50000)
the space 12288K, 3% used [0x33b50000, 0x33bae758, 0x33bae800, 0x34750000)
ro space 10240K, 55% used [0x37b50000, 0x380d1140, 0x380d1200, 0x38550000)
rw space 12288K, 55% used [0x38550000, 0x38bf44c8, 0x38bf4600, 0x39150000)
Warning: The flag -HandlePromotionFailure has been EOL'd as of 6.0_24 and will be ignored
*/
}