对象肯定是在java的堆中进行分配,一般情况下分配在新生代的Eden中,如果对象很大,可能对象直接进入老年代。如果在堆中,线程申请了TLAB本地线程缓存,则在缓存中存储对象。由于垃圾收集器的不同,则对象的分配策略也是不同的,以下是集中内存分配的策略:

1、对象优先在Eden区域中分配

当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。例如:

public class MinorGC {
private static final int _1MB = 1024 * 1024;

/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -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
}
public static void main(String[] args) {
MinorGC gc=new MinorGC();
gc.testAllocation();
}
}

堆的最小值-Xms20M 堆的最大值-Xmx20M 新生代的大小是 -Xmn10M      -XX:SurvivorRatio=8则Eden:Survivor=8:1即8M:1M存放对象空间的大小为9M(9216K)

程序执行的过程是,首先6M存放在Eden中,当程序继续存储4M的时候,Eden空间不够,发起Minor GC。但是Survivor空间只有1M装载不下6M,所有就向老年代申请空间,存放在老年代中,allocation4在Eden空间中进行分配4M。

//以下是产生的GC日志

[GC [DefNew: 6471K->137K(9216K), 0.0039742 secs] 6471K->6281K(19456K), 0.0040027 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4396K [0x266d0000, 0x270d0000, 0x270d0000)
  eden space 8192K,  52% used [0x266d0000, 0x26af8fd8, 0x26ed0000)
  from space 1024K,  13% used [0x26fd0000, 0x26ff2418, 0x270d0000)
  to   space 1024K,   0% used [0x26ed0000, 0x26ed0000, 0x26fd0000)
 tenured generation   total 10240K, used 6144K [0x270d0000, 0x27ad0000, 0x27ad0000)
   the space 10240K,  60% used [0x270d0000, 0x276d0030, 0x276d0200, 0x27ad0000)
 compacting perm gen  total 12288K, used 361K [0x27ad0000, 0x286d0000, 0x2bad0000)
   the space 12288K,   2% used [0x27ad0000, 0x27b2a7b8, 0x27b2a800, 0x286d0000)
    ro space 8192K,  63% used [0x2bad0000, 0x2bfe8b20, 0x2bfe8c00, 0x2c2d0000)
    rw space 12288K,  53% used [0x2c2d0000, 0x2c945138, 0x2c945200, 0x2ced0000)

Minor GC 主要发生在新生代的垃圾收集动作,因为java的大多数对象都是朝生熄灭的,Minor GC非常频繁,速度快

Full GC 主要发生在老年代,经常伴随Minor GC的发生。

2、大对象直接进入老年代

如何设置对象的大小的邻接值呢?就是-XX: PretenureSizeThreshold这个命令设置对象的大小。这个命令对Serial和ParNew收集器好使,其他的垃圾收集器不好使。例如:

package GcAndMemory;

public class TestPretenureSizeThreshold {
private static final int _1MB = 1024 * 1024;

/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728
*/
public static void testPretenureSizeThreshold() {
byte[] allocation;
allocation = new byte[4 * _1MB];  //直接分配在老年代中
}
public static void main(String[] args) {
TestPretenureSizeThreshold a=new TestPretenureSizeThreshold();
a.testPretenureSizeThreshold();
}
}

产生如下的GC日志

Heap
 def new generation   total 9216K, used 491K [0x266d0000, 0x270d0000, 0x270d0000)
  eden space 8192K,   6% used [0x266d0000, 0x2674af18, 0x26ed0000)
  from space 1024K,   0% used [0x26ed0000, 0x26ed0000, 0x26fd0000)
  to   space 1024K,   0% used [0x26fd0000, 0x26fd0000, 0x270d0000)
 tenured generation   total 10240K, used 4096K [0x270d0000, 0x27ad0000, 0x27ad0000)
   the space 10240K,  40% used [0x270d0000, 0x274d0010, 0x274d0200, 0x27ad0000)
 compacting perm gen  total 12288K, used 361K [0x27ad0000, 0x286d0000, 0x2bad0000)
   the space 12288K,   2% used [0x27ad0000, 0x27b2a780, 0x27b2a800, 0x286d0000)
    ro space 8192K,  63% used [0x2bad0000, 0x2bfe8b20, 0x2bfe8c00, 0x2c2d0000)
    rw space 12288K,  53% used [0x2c2d0000, 0x2c945138, 0x2c945200, 0x2ced0000)

该对象是4M>3M所以该对象直接进入老年代,为什么大对象要直接进入老年代呢?因为如果大对象存放在Eden中,该空间很难再存放其他的对象,会经常触发GC进行回收,不断的将对象复制到Survivor中,如果Survivor空间装载不下,就要放到老年代中。

3、长期存活的对象进入老年代

JVM虚拟机给每一个对象定义一个年龄计数器,如果该对象在Minor GC没有被回收,并且被放到Survivor区域中,该对象的年龄+1,当该对象的年龄增加到MaxTurningThreshold 设置的值时,该对象就进入老年代。

package GcAndMemory;


public class TestMaxTurningThreshold {
private static final int _1MB = 1024 * 1024;


/**
* 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]; // 什么时候进入老年代决定于XX:MaxTenuringThreshold设置
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
}

public static void main(String[] args) {
TestMaxTurningThreshold a=new TestMaxTurningThreshold();
a.testTenuringThreshold();
}
}

程序执行的过程就是,首先在Eden中分配8M+1/4M,当allocation3 = null;时触发一次GC,将活着的对象进行复制算法,1/4M可以放入到Survivor区域中,该对象的年龄+1,并且达到了MAxTurningThreshold的值,所以该对象进入老年代了,而allocation2是4M,Survivor放不下,由老年代担保直接进入到老年代中占4M,最后又在Eden中分配4M,所以eden space 8192K,  52% used(4M/8M) 而tenured generationthe space 10240K,  43%(4M+1/4M/10M)

[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:     402536 bytes,     402536 total
: 4679K->393K(9216K), 0.0027265 secs] 4679K->4489K(19456K), 0.0027439 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
: 4489K->0K(9216K), 0.0004532 secs] 8585K->4488K(19456K), 0.0004660 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4259K [0x266d0000, 0x270d0000, 0x270d0000)
  eden space 8192K,  52% used [0x266d0000, 0x26af8fd8, 0x26ed0000)
  from space 1024K,   0% used [0x26ed0000, 0x26ed0000, 0x26fd0000)

  to   space 1024K,   0% used [0x26fd0000, 0x26fd0000, 0x270d0000)
 tenured generation   total 10240K, used 4488K [0x270d0000, 0x27ad0000, 0x27ad0000)
   the space 10240K,  43% used [0x270d0000, 0x275323e8, 0x27532400, 0x27ad0000)
 compacting perm gen  total 12288K, used 361K [0x27ad0000, 0x286d0000, 0x2bad0000)
   the space 12288K,   2% used [0x27ad0000, 0x27b2a7d8, 0x27b2a800, 0x286d0000)
    ro space 8192K,  63% used [0x2bad0000, 0x2bfe8b20, 0x2bfe8c00, 0x2c2d0000)
    rw space 12288K,  53% used [0x2c2d0000, 0x2c945138, 0x2c945200, 0x2ced0000)

如果将MaxTruningThreshold=15,GC日志如下:

[GC [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age   1:     402536 bytes,     402536 total
: 4679K->393K(9216K), 0.0025194 secs] 4679K->4489K(19456K), 0.0025473 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:     402392 bytes,     402392 total
: 4489K->392K(9216K), 0.0003865 secs] 8585K->4488K(19456K), 0.0003977 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4652K [0x266d0000, 0x270d0000, 0x270d0000)
  eden space 8192K,  52% used [0x266d0000, 0x26af8fd8, 0x26ed0000)
  from space 1024K,  38% used [0x26ed0000, 0x26f323d8, 0x26fd0000)

  to   space 1024K,   0% used [0x26fd0000, 0x26fd0000, 0x270d0000)
 tenured generation   total 10240K, used 4096K [0x270d0000, 0x27ad0000, 0x27ad0000)
   the space 10240K,  40% used [0x270d0000, 0x274d0010, 0x274d0200, 0x27ad0000)
 compacting perm gen  total 12288K, used 361K [0x27ad0000, 0x286d0000, 0x2bad0000)
   the space 12288K,   2% used [0x27ad0000, 0x27b2a7d8, 0x27b2a800, 0x286d0000)
    ro space 8192K,  63% used [0x2bad0000, 0x2bfe8b20, 0x2bfe8c00, 0x2c2d0000)
    rw space 12288K,  53% used [0x2c2d0000, 0x2c945138, 0x2c945200, 0x2ced0000)

4、动态年龄的判断

虚拟机并不是永远的要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,如果Survivor空间中的相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于改年龄的对象可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。例如:

package GcAndMemory;


public class TestMaxTenuringThreshold {
private static final int _1MB = 1024 * 1024;


/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:+PrintTenuringDistribution
*/
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];
}

public static void main(String[] args) {
TestMaxTenuringThreshold a=new TestMaxTenuringThreshold();
a.testTenuringThreshold2();
}

}

[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age   1:     664696 bytes,     664696 total
: 4935K->649K(9216K), 0.0023879 secs] 4935K->4745K(19456K), 0.0024084 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
: 4745K->0K(9216K), 0.0005401 secs] 8841K->4744K(19456K), 0.0005523 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4259K [0x266d0000, 0x270d0000, 0x270d0000)
  eden space 8192K,  52% used [0x266d0000, 0x26af8fd8, 0x26ed0000)
  from space 1024K,   0% used [0x26ed0000, 0x26ed0000, 0x26fd0000)

  to   space 1024K,   0% used [0x26fd0000, 0x26fd0000, 0x270d0000)
 tenured generation   total 10240K, used 4744K [0x270d0000, 0x27ad0000, 0x27ad0000)
   the space 10240K,  46% used [0x270d0000, 0x275723f8, 0x27572400, 0x27ad0000)
 compacting perm gen  total 12288K, used 362K [0x27ad0000, 0x286d0000, 0x2bad0000)
   the space 12288K,   2% used [0x27ad0000, 0x27b2a810, 0x27b2aa00, 0x286d0000)
    ro space 8192K,  63% used [0x2bad0000, 0x2bfe8b20, 0x2bfe8c00, 0x2c2d0000)
    rw space 12288K,  53% used [0x2c2d0000, 0x2c945138, 0x2c945200, 0x2ced0000)

5、空间分配担保

当新生代要进行Minor GC时候,首先会检查老年代最大的连续空间时候可以装载下新生代中的存活的对象,如果大于,则可以进行安全的Minor GC,否则,查看时候满足HandlePromationFailure 允许担保失败,检查老年代的最大连续空间是否大于平均晋升到老年代中的对象,如果大于,可以进行Minor GC,否则 需要老年代需要进行一个FullGC操作,使用标记-清除算法,为老年代提供更大的连续空间。

package GcAndMemory;


public class TestHandlePromotionFailure {


private static final int _1MB = 1024 * 1024;


/**
* VM参数:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure
*/
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];
}


public static void main(String[] args) {
TestHandlePromotionFailure a=new TestHandlePromotionFailure();
a.testHandlePromotion();
}
}

[GC [DefNew: 6471K->137K(9216K), 0.0027766 secs] 6471K->4233K(19456K), 0.0027945 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[GC [DefNew: 6367K->6367K(9216K), 0.0000083 secs][Tenured: 4096K->4232K(10240K), 0.0051003 secs] 10463K->4232K(19456K), [Perm : 362K->362K(12288K)], 0.0051378 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 2211K [0x266d0000, 0x270d0000, 0x270d0000)
  eden space 8192K,  27% used [0x266d0000, 0x268f8fd8, 0x26ed0000)
  from space 1024K,   0% used [0x26fd0000, 0x26fd0000, 0x270d0000)
  to   space 1024K,   0% used [0x26ed0000, 0x26ed0000, 0x26fd0000)
 tenured generation   total 10240K, used 4232K [0x270d0000, 0x27ad0000, 0x27ad0000)
   the space 10240K,  41% used [0x270d0000, 0x274f23e8, 0x274f2400, 0x27ad0000)
 compacting perm gen  total 12288K, used 362K [0x27ad0000, 0x286d0000, 0x2bad0000)
   the space 12288K,   2% used [0x27ad0000, 0x27b2a8b8, 0x27b2aa00, 0x286d0000)
    ro space 8192K,  63% used [0x2bad0000, 0x2bfe8b20, 0x2bfe8c00, 0x2c2d0000)
    rw space 12288K,  53% used [0x2c2d0000, 0x2c945138, 0x2c945200, 0x2ced0000)

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐