[JVM]内存分配策略

1、优先分配到eden

package 深入理解java虚拟机;
 
public class 对象优先分配到eden区 {
 
	/**
	 * 1M的内存大小
	 */
	private static final int _1MB = 1024 * 1024;
 
	/**
	 * jvm参数设置:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
	 * 
	 * 1、打印垃圾回收日志信息:-verbose:gc -XX:+PrintGCDetails
	 * 2、设置jvm初始堆内存为20M:-Xms20M
	 * 3、设置jvm最大堆内存为20M:-Xmx20M
	 * 4、设置新生代堆内存大小为10M:-Xmn10M
	 * 5、设置新生代中Eden区与一个Survivor区的空间比例是8:1:-XX:SurvivorRatio=8
	 * 
	 * @author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	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
	}
	
	/**
	 * 运行结果如下:
	 * 
	 * Heap
	 *  PSYoungGen      total 9216K, used 7291K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
	 *  eden space 8192K, 89% used [0x00000000ff600000,0x00000000ffd1ef00,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)
	 *  Metaspace       used 2669K, capacity 4486K, committed 4864K, reserved 1056768K
	 *  class space    used 288K, capacity 386K, committed 512K, reserved 1048576K
	 * 
	 * PSYoungGen表示当前所使用的垃圾收集器是Parallel,可使用参数-XX:+UseSerialGC 来指定jvm使用Serial
	 * 
	 * 使用Serial收集器的运行结果如下:
	 * 
	 * [GC (Allocation Failure) [DefNew: 7127K->522K(9216K), 0.0033855 secs] 7127K->6666K(19456K), 0.0034315 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
	 * Heap
	 *  def new generation   total 9216K, used 4701K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
	 *  eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
	 *  from space 1024K,  51% used [0x00000000ff500000, 0x00000000ff582ad0, 0x00000000ff600000)
	 *  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
	 *  tenured generation   total 10240K, used 6144K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
	 *  the space 10240K,  60% used [0x00000000ff600000, 0x00000000ffc00030, 0x00000000ffc00200, 0x0000000100000000)
	 *  Metaspace       used 2669K, capacity 4486K, committed 4864K, reserved 1056768K
	 *  class space    used 288K, capacity 386K, committed 512K, reserved 1048576K
	 * 
	 * 可见,Serial收集器使用的是def new generation来标识新生代的
	 * 新生代的总内存是10M,eden区占8M,两个Survivor区各占1M
	 * 在分配allocation1到allocation3的时候,eden区的空间是够用的,此时eden区占用了6M,剩余2M
	 * 当分配allocation4的时候,由于allocation4需要4M的内存,此时eden区的空间已经不够用了,所以jvm就触发了一次新生代的垃圾回收
	 * 新生代的垃圾回收使用了复制算法,是将新生代中的所有存活的对象移动到Survivor中。
	 * 由于Survivor区只有1M的内存,很明显三个对象都放不下,此时就必须触发内存担保机制了,即将三个对象移动到老年代中。
	 * 此时eden区就是出于空闲状态了,所以allocation4就成功的被分配到了eden区中。
	 * 所以我们看到的结果就是最终新生代使用了4M内存,而老年代使用了6M内存,而从另一方面也可以看出对象创建时是优先分配到eden区中的。
	 * 
	 * @author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	public static void main(String[] args) {
		testAllocation();
	}
}

2、大对象直接分配到老年代

public class 大对象直接分配到老年代 {
 
	private static final int _1MB = 1024 * 1024;
 
	/**
	 * jvm参数设置:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
	 * -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728
	 * 
	 * 1、指定内存占用大于3145728的对象直接分配到老年代:-XX:PretenureSizeThreshold=3145728
	 * (3145728=3M=3*1024*1024,超过3M的对象会被直接放到老年代中)
	 * 
	 * 2、PretenureSizeThreshold只针对Serial和ParNew有效
	 * 
	 * @author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	public static void testPretenureSizeThreshold() {
		byte[] allocation;
		allocation = new byte[4 * _1MB]; // 直接分配在老年代中
	}
 
}

3、长期存活的对象分配到老年代

public class 长期存活的对象分配到老年代 {
 
	private static final int _1MB = 1024 * 1024;
 
	/**
	 * jvm参数设置:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
	 * -XX:SurvivorRatio=8 -XX:+PrintTenuringDistribution
	 * -XX:MaxTenuringThreshold=1
	 * 
	 * 1、对象在新生代中每经历过一次Minor GC,若对象进入存活区的话,则将对象的年龄都会加1,当对象的年龄到达某一个值时,对象就会进入老年代
	 * 
	 * 2、设置进入老年代年龄的阈值为1:-XX:MaxTenuringThreshold=1
	 * 
	 *@author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	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];
	}
 
}

4、动态对象年龄判断

  • 为了更好的适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须到达了MaxTenuringThreshold才能晋升到老年代。

  • 如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。

5、空间分配担保(eden区内存不够,到老年代那去借)

public class 空间分配担保 {
 
	private static final int _1MB = 1024 * 1024;
 
	/**
	 * jvm参数设置:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
	 * -XX:-HandlePromotionFailure
	 * 
	 * 1、此设置中,eden区有8M,两个Survivor区各占1M,老年代占10M
	 * 2、每次进行Minor GC之前,虚拟机会先检查老年代的最大可用的连续空间是否大于新生代所有对象的空间
	 * 3、如果是的话,则Minor GC是安全的
	 * 4、如果不是的话,则虚拟机先看HandlePromotionFailure的值,如果值设置成-的话,则虚拟机需要启动一次Full GC让老年代腾出更多的空间
	 * 5、如果HandlePromotionFailure的值设置成+的话,则会检查老年代中的最大可用连续空间是否大于历次晋升到老年代对象的平均大小
	 * 6、如果不是的话,则会进行一次Full GC,如果是的话,虚拟机则会尝试一次Minor GC,尽管它是有风险的
	 * 7、如果Minor GC失败了,则会重新发起一次Full GC
	 * 8、虽然担保失败时绕的圈子是最大的,但大多数情况下还是会将HandlePromotionFailure打开,避免Full GC过于频繁
	 * 
	 * 注:HandlePromotionFailure参数只在JDK6之前有效,JDK6之后该值不可变且默认为打开。
	 * 
	 * @author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	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];
	}
}

6、逃逸分析与栈上分配

public class 逃逸分析与栈上分配 {
 
	public String str;
	
	/**
	 * 方法返回str,对象发生了逃逸 
	 * 
	 *@author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	public String getStr() {
		return str == null ? new String() : str;
	}
	
	/**
	 * 为成员属性赋值,发生逃逸
	 * 
	 * @author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	public void setStr() {
		this.str = new String();
	}
	
	/**
	 * 对象的作用域仅在当前方法中有效,没有发生逃逸(
	 * 
	 * (只要对象不发生逃逸,那么就会把对象分配到栈内存中去。对象随栈帧的消失而消失)
	 * 
	 * @author huangxj 2018年1月21日 
	 *
	 * @version v1.0
	 */
	public void useStr() {
		String str = new String();
	}
	
	/**
	 * 引用成员变量的值,发生逃逸
	 * 
	 * @author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	public void useStr2() {
		String str = getStr();
	}
}

 

你可能感兴趣的:(jvm,轻松理解JVM)