内存分配
Memory Allocation
对象的内存分配,往大方向上讲,就是在堆上分配
- 新的对象和数组被创建并放入老年代。
Minor 垃圾回收将发生在新生代。依旧存活的对象将从eden 区移到survivor 区。Major 垃圾回收一般会导致应用进程暂停,它将在三个区内移动对象。仍然存活的对象将被从新生代移动到老年代。- 每次进行老年代回收时也会进行永久代回收。它们之中任何一个变满时,都会进行回收。
接下来我们将会讲解几条最普遍的内存分配规则,并通过代码去验证这些规则。本节中的代码在测试时使用
虚拟地址
在
内存分配
一般
内存分配状态
一个大的进程如果初始化需要分配一块大的内存空间,内存空间一般会经历两个状态的转换过程,首先内存必须是
Eden: 优先分配新生成对象
新生成对象在HeapSize 中变化
java.lang.Class.forName
进行动态加载。
Minor GC
大多数情况下,对象在新生代
private static final int _1MB = 1024 * 1024;
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
*/
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: 6651K->148K(9216K), 0.0070106 secs]
6651K->6292K(19456K), 0.0070426 secs] [Times:
user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4326K
[0x029d0000, 0x033d0000, 0x033d0000)
eden space 8192K, 51% used [0x029d0000,
0x02de4828, 0x031d0000)
from space 1024K, 14% used [0x032d0000,
0x032f5370, 0x033d0000)
to space 1024K, 0% used [0x031d0000,
0x031d0000, 0x032d0000)
tenured generation total 10240K, used 6144K
[0x033d0000, 0x03dd0000, 0x03dd0000)
the space 10240K, 60% used [0x033d0000,
0x039d0030, 0x039d0200, 0x03dd0000)
compacting perm gen total 12288K, used 2114K
[0x03dd0000, 0x049d0000, 0x07dd0000)
the space 12288K, 17% used [0x03dd0000,
0x03fe0998, 0x03fe0a00, 0x049d0000)
No shared spaces configured.
大对象直接进入老年代
所谓大对象就是指,需要大量连续内存空间的
private static final int _1MB = 1024 * 1024;
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
* -XX:PretenureSizeThreshold=3145728
*/
public static void testPretenureSizeThreshold() {
byte[] allocation;
allocation = new byte[4 * _1MB]; //直接分配在老年代中
}
运行结果
Heap
def new generation total 9216K, used 671K
[0x029d0000, 0x033d0000, 0x033d0000)
eden space 8192K, 8% used [0x029d0000,
0x02a77e98, 0x031d0000)
from space 1024K, 0% used [0x031d0000, 0x031d0000, 0x032d0000)
to space 1024K, 0% used [0x032d0000, 0x032d0000, 0x033d0000)
tenured generation total 10240K, used 4096K
[0x033d0000, 0x03dd0000, 0x03dd0000)
the space 10240K, 40% used [0x033d0000,
0x037d0010, 0x037d0200, 0x03dd0000)
compacting perm gen total 12288K, used 2107K
[0x03dd0000, 0x049d0000, 0x07dd0000)
the space 12288K, 17% used [0x03dd0000,
0x03fdefd0, 0x03fdf000, 0x049d0000)
No shared spaces configured.
长期存活的对象直接进入老年代
虚拟机既然采用了分代收集的思想来管理内存,那内存回收时就必须能识别哪些对象应当放在新生代,哪些对象应放在老年代中。为了做到这点,虚拟机给每 个对象定义了一个对象年龄
private static final int _1MB = 1024 * 1024;
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M
-XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1
* -XX:+PrintTenuringDistribution
*/
@SuppressWarnings("unused")
public static void testTenuringThreshold() {
byte[] allocation1, allocation2, allocation3;
allocation1 = new byte[_1MB / 4];
// 什么时候进入老年代取决于XX:MaxTenuringThreshold设置
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
}
以
[GC [DefNew
Desired Survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 414664 bytes, 414664 total
: 4859K->404K(9216K), 0.0065012 secs] 4859K->4500K
(19456K), 0.0065283 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[GC [DefNew
Desired Survivor size 524288 bytes, new threshold 1 (max 1)
: 4500K->0K(9216K), 0.0009253 secs] 8596K->4500K
(19456K), 0.0009458 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4178K
[0x029d0000, 0x033d0000, 0x033d0000)
eden space 8192K, 51% used [0x029d0000,
0x02de4828, 0x031d0000)
from space 1024K, 0% used [0x031d0000,
0x031d0000, 0x032d0000)
to space 1024K, 0% used [0x032d0000,
0x032d0000, 0x033d0000)
tenured generation total 10240K, used
4500K [0x033d0000, 0x03dd0000, 0x03dd0000)
the space 10240K, 43% used [0x033d0000,
0x03835348, 0x03835400, 0x03dd0000)
compacting perm gen total 12288K, used 2114K
[0x03dd0000, 0x049d0000, 0x07dd0000)
the space 12288K, 17% used [0x03dd0000,
0x03fe0998, 0x03fe0a00, 0x049d0000)
No shared spaces configured.
以
[GC [DefNew
Desired Survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 414664 bytes, 414664 total
: 4859K->404K(9216K), 0.0049637 secs] 4859K->
4500K(19456K), 0.0049932 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 2: 414520 bytes, 414520 total
: 4500K->404K(9216K), 0.0008091 secs] 8596K->
4500K(19456K), 0.0008305 secs] [Times: user=
0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4582K
[0x029d0000, 0x033d0000, 0x033d0000)
eden space 8192K, 51% used [0x029d0000, 0x02de4828, 0x031d0000)
from space 1024K, 39% used [0x031d0000, 0x03235338, 0x032d0000)
to space 1024K, 0% used [0x032d0000, 0x032d0000, 0x033d0000)
tenured generation total 10240K, used 4096K
[0x033d0000, 0x03dd0000, 0x03dd0000)
the space 10240K, 40% used [0x033d0000,
0x037d0010, 0x037d0200, 0x03dd0000)
compacting perm gen total 12288K, used 2114K
[0x03dd0000, 0x049d0000, 0x07dd0000)
the space 12288K, 17% used [0x03dd0000,
0x03fe0998, 0x03fe0a00, 0x049d0000)
No shared spaces configured.
动态对象年龄
在说明下,以下三种情况对象会被晋升到old区域:
1、在eden和survivor中可以来回被minor gc多次,这个次数超过了-XX:MaxTenuringThreshold
2、在发生minor gc时,发现to survivor无法放下这些对象,就会进入old。
3、在新申请对象,大于eden区域的一半大小时直接进入old,也可以专门设置参数-XX:PretenureSizeThreshold这个参数指定当超过这个值就直接进入old。
当上面的对象被移动到了Tenured区域,这个区域一般非常大,占用了HeapSize的绝大部分空间,此时若它发生一次内存回收,就不能像刚才那样来 回拷贝了,那样代价太大,而且这个区域可以说是经得起考验的对象才会被移动过来,在概率上是不容易被销毁掉的对象才会被移动过来;那么,我们很此时想到的 就是反过来计算,也就是找到需要销毁的对象,将其销毁,关于算法也是下面第三章要说的内容,总之对象会在这里存放着。
为了能更好地适应不同程序的内存状况,虚拟机并不总是要求对象的年龄必须达到
private static final int _1MB = 1024 * 1024;
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M
-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大于survivor空间的一半
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: 676824 bytes, 676824 total
: 5115K->660K(9216K), 0.0050136 secs] 5115K->
4756K(19456K), 0.0050443 secs] [Times: user=0.00
sys=0.01, real=0.01 secs]
[GC [DefNew
Desired Survivor size 524288 bytes, new threshold 15 (max 15)
: 4756K->0K(9216K), 0.0010571 secs] 8852K->4756K
(19456K), 0.0011009 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4178K
[0x029d0000, 0x033d0000, 0x033d0000)
eden space 8192K, 51% used [0x029d0000, 0x02de4828, 0x031d0000)
from space 1024K, 0% used [0x031d0000, 0x031d0000, 0x032d0000)
to space 1024K, 0% used [0x032d0000, 0x032d0000, 0x033d0000)
tenured generation total 10240K, used 4756K
[0x033d0000, 0x03dd0000, 0x03dd0000)
the space 10240K, 46% used [0x033d0000,
0x038753e8, 0x03875400, 0x03dd0000)
compacting perm gen total 12288K, used 2114K
[0x03dd0000, 0x049d0000, 0x07dd0000)
the space 12288K, 17% used [0x03dd0000,
0x03fe09a0, 0x03fe0a00, 0x049d0000)
No shared spaces configured.
空间分配担保
在发生
private static final int _1MB = 1024 * 1024;
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M
-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];
}
以
[GC [DefNew: 6651K->148K(9216K), 0.0078936 secs]
6651K->4244K(19456K), 0.0079192 secs] [Times:
user=0.00 sys=0.02, real=0.02 secs]
[GC [DefNew: 6378K->6378K(9216K), 0.0000206 secs]
[Tenured: 4096K->4244K(10240K), 0.0042901 secs]
10474K->4244K(19456K), [Perm : 2104K->2104K(12288K)],
0.0043613 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
以MaxTenuringThreshold= true的参数设置来运行的结果:
[GC [DefNew: 6651K->148K(9216K), 0.0054913 secs]
6651K->4244K(19456K), 0.0055327 secs] [Times:
user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6378K->148K(9216K), 0.0006584 secs]
10474K->4244K(19456K), 0.0006857 secs] [Times:
user=0.00 sys=0.00, real=0.00 secs]