对象优先在Eden分配
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起 一次Minor GC。
大对象直接进入老年代
大对象就是指需要大量连续内存空间的Java对象,最典型的大对象便是那种很长的字符串,或者 元素数量很庞大的数组,本节例子中的byte[]数组就是典型的大对象。大对象对虚拟机的内存分配来说 就是一个不折不扣的坏消息,比遇到一个大对象更加坏的消息就是遇到一群“朝生夕灭”的“短命大对 象”,我们写程序的时候应注意避免。在Java虚拟机中要避免大对象的原因是,在分配空间时,它容易 导致内存明明还有不少空间时就提前触发垃圾收集,以获取足够的连续空间才能安置好它们,而当复 制对象时,大对象就意味着高额的内存复制开销。HotSpot虚拟机提供了-XX:PretenureSizeThreshold 参数,指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在Eden区及两个Survivor区 之间来回复制,产生大量的内存复制操作。
/**
* @author Wen
* 代码清单3-8 大对象直接进入老年代
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728 -XX:+UseSerialGC
*/
public class test1 {
private static final int _1MB = 1024 * 1024;
public static void testPretenureSizeThreshold() {
byte[] allocation;
//直接分配在老年代中
allocation = new byte[4 * _1MB];
}
public static void main(String[] args) {
testPretenureSizeThreshold();
}
}
Heap
def new generation total 9216K, used 3054K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 9% used [0x00000000fec00000, 0x00000000feefbb38, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 6144K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 40% used [0x00000000ff600000, 0x00000000ffc00010, 0x00000000ffc00200, 0x0000000100000000)
Metaspace used 3250K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 350K, capacity 388K, committed 512K, reserved 1048576K
来 假装分析一波
首先我们通过-XX:SurvivorRatio=8这个参数来指定Eden和Survivor的From、To区域的内存比例为8:1:1。
-Xms20M -Xmx20M -Xmn10M 来指定堆的大小为20M不可扩展且设置年轻代的大小为10M,
通过设置-XX:PretenureSizeThreshold=3145728的大小来限制当对象大于这值时,直接进入老年代.
通过日志打印可以看出Survivor区域并没有被使用,老年代被使用了40%所以我们创建的allocation 直接被分配到了老年代
长期存活的对象将进入老年代
HotSpot虚拟机中多数收集器都采用了分代收集来管理堆内存,那内存回收时就必须能决策哪些存 活对象应当放在新生代,哪些存活对象放在老年代中。为做到这点,虚拟机给每个对象定义了一个对 象年龄(Age)计数器,存储在对象头中。对象通常在Eden区里诞生,如果经过第一次 Minor GC后仍然存活,并且能被Survivor容纳的话,该对象会被移动到Survivor空间中,并且将其对象 年龄设为1岁。对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程 度(默认为15),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX: MaxTenuringThreshold设置。
当-XX:MaxTenuringThreshold=1
/**
* @author Wen
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution -XX:+UseSerialGC
*/
public class test2 {
private static final int _1MB = 1024 * 1024;
@SuppressWarnings("unused")
public static void testTenuringThreshold() {
byte[] allocation1, allocation2, allocation3;
// 什么时候进入老年代决定于XX:MaxTenuringThreshold设置
allocation1 = new byte[_1MB / 4];
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
}
public static void main(String[] args) {
testTenuringThreshold();
}
}
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 1048576 bytes, 1048576 total
: 7243K->1024K(9216K), 0.0029620 secs] 7243K->5314K(19456K), 0.0029979 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 640 bytes, 640 total
: 5203K->0K(9216K), 0.0010193 secs] 9494K->5294K(19456K), 0.0010311 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4233K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 51% used [0x00000000fec00000, 0x00000000ff022540, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400280, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 5293K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 51% used [0x00000000ff600000, 0x00000000ffb2b7b8, 0x00000000ffb2b800, 0x0000000100000000)
Metaspace used 3246K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 350K, capacity 388K, committed 512K, reserved 1048576K
Process finished with exit code 0
来 假装分析er波
allocation1 = 0.25M 进入Eden = 0.25M
allocation2 = 4M 进入Eden = 4 + 0.25M
allocation3 = 4M 进入Eden = 8.25 > 8M,这时候Eden内存被占满,触发GC
这时候Survivor也放不下allocation2和allocation3 ,但是可以放下allocation1 ,这时候allocation1 进入From = 0.25M,分代年龄+1,
allocation2 进入老年代 = 4M,allocation3 进入Eden = 4M。
之后allocation3 = 4M 进入Eden,Eden满了触发GC,但是由于我们设置了-XX:MaxTenuringThreshold=1,意味着分代年龄达大于1的对象将送往老年代,
动态对象年龄判定
为了能更好地适应不同程序的内存状况,HotSpot虚拟机并不是永远要求对象的年龄必须达到- XX:MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于 Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到-XX: MaxTenuringThreshold中要求的年龄。
/**
* -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:+UseSerialGC -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:+PrintTenuringDistribution
* @author Wen
*/
public class Test3 {
private static final int _1MB = 1024 * 1024;
public static void test(){
byte[] allocation1,allocation2,allocation3,allocation4;
// allocation1+allocation2大于survivo空间一半
allocation1 = new byte[_1MB / 4];
allocation2 = new byte[_1MB / 4];
allocation3 = new byte[4 * _1MB];
allocation4 = new byte[4 * _1MB];
allocation4 = null;
allocation4 = new byte[4 * _1MB];
}
public static void main(String[] args) {
test();
}
}
参数分析
-Xms20M:分配堆的最小内存
-Xmx20M:分配堆的最大内存(-Xms =-Xmx表示设定堆不可动态扩展内存)
-Xmn10M:分配年轻代的内存
-XX:+PrintGCDetails:在控制台打印日志
-XX:+UseSerialGC:指定年轻代使用Serial搜集器
-XX:SurvivorRatio=8:指定Eden与survivor中Form区和To区内存占比为8:1:1
-XX:MaxTenuringThreshold=15:设置对象分代年龄达到15后进入老年代区域
-XX:+PrintTenuringDistribution:打印分代年龄
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age 1: 1048568 bytes, 1048568 total
: 7499K->1023K(9216K), 0.0034811 secs] 7499K->5553K(19456K), 0.0035137 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 400 bytes, 400 total
: 5203K->0K(9216K), 0.0023783 secs] 9733K->9649K(19456K), 0.0023921 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4234K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 51% used [0x00000000fec00000, 0x00000000ff022758, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400190, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 9649K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 94% used [0x00000000ff600000, 0x00000000fff6c558, 0x00000000fff6c600, 0x0000000100000000)
Metaspace used 3279K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 355K, capacity 388K, committed 512K, reserved 1048576K
来 假装分析er波
allocation1 进入Eden = _1MB/4 < 8M
allocation2 进入Eden = _1MB/4 + _1MB/4 = _1MB/2 < 8M
allocation3 进入Eden = _1MB/2 + 4M < 8M
allocation4 进入Eden = _1MB/2 + 4M + 4M > 8M 触发GC,这时候由于Form和To都放不下allocation3,所以
allocation3直接进入老年代,allocation1、allocation2进入From区分代年龄+1,allocation4 进入Eden = 4M
allocation4 = null 为后面清理未被使用的对象做准备
allocation4 = new byte[4 * _1MB] 进入Eden = 4M + 4M >8M(因为这里还要算上对象头和内存
补白的大小所以大于8M),触发GC,发现前面allocation4 = null 的对象未被使用,直接清除
这时候allocation1 + allocation2 大于from区_1MB/2(满足分代年龄相等且内存总和大于survivor的一半)
直接进入老年代,Eden = 4M,Survivor = 0M,老年代 >4+_1MB/2