跟我一起來研究Java内存管理

先看下面这个小程序

/**
 * 这个小程序作用是创建一个大约1MB的对象
 * 以下参数为运行设置中的VM Arguments参数
 * -verbose:gc
 * -XX:+PrintGCDetails
 * -Xms20M 
 * -Xmx20M
 * -Xmn10M
 * @author qianl
 *
 */
public class Test {

	public static int _1M = 1024 * 1024;
	
	public static void main(String[] args) throws InterruptedException {
		byte[] b1;
		b1 = new byte[1 * _1M];
	}

}

先解释一下VM Arguments参数:
* -verbose:gc : 输出GC的详细信息
* -XX:+PrintGCDetails : 打印GC工作的内存变化详细信息
* -Xms20M : 堆最小值
* -Xmx20M : 堆最大值
* -Xmn10M : 分配给新生代区(后面将会讲解)的大小

下面是运行后,控制台的输出:


如果英文不好,看着费劲的,下面我照着翻译一下

接下来,我们一起來分析一下

在VM Arguments设置中,我们将堆总空间设置为20M,且不可扩展,其中10M分给新生代,剩余10M分给了老年代。可能有个疑问了,中间的 “PSOldGen total 10240K”,确实刚好有10M,可是,这一句 “PSYoungGen total 8960K, used 1485K” ,哪有10M。大家看这一句下面的信息,eden的7680K,from space的1280K,to space的1280K,这三个加起来,是不是刚好10240K = 10M呢。

这里面有个细节,新生代大小,具体指的是一个eden区加上一个Survivor(就是上面的from space和to space)区。我们给新生代分配的10M,实际上是分给了三个区域,一个eden区和两个Survivor区,从数字可以看出,它们默认的分配比例为8:1:1,当然,可以手动设置的,感兴趣的可以自己查一下。所以上面中,新生代的total为8960K(eden:7680K,Survivor:1280K)。
PS : 我在网上看了一下,对于Survivor还有另外一种解释,from space和to space共同构成Survivor区,eden和Survivor构成新生代区,如果这样,那么PSYoungGen(新生代区)就应该是10240K,但实际PSYoungGen只有8960K,它没算上to space区。至于那种好,各位因人而异吧。

这里解释一下这三个区的区别:
* PSYoungGen : 主要存放新生的对象
* PSOldGen : 主要存放应用程序中生命周期长的对象
* PSPermGen : 永久保存区,方法区(参见我的另外一篇《Java虚拟机内存管理》)的对象放置在此区域

那么,Java虚拟机,是怎样安排放置对象到这些区域的呢?将上面的程序改一下:

/**
 * 此程序创建了两个大约7M的对象
 * 以下参数为运行设置中的VM Arguments参数
 * -verbose:gc
 * -XX:+PrintGCDetails
 * -Xms20M 
 * -Xmx20M
 * -Xmn10M
 * @author qianl
 *
 */
public class Test {

	public static int _1M = 1024 * 1024;

	
	public static void main(String[] args) throws InterruptedException {
		byte[] b1,b2;
		b1 = new byte[7 * _1M];
		b2 = new byte[7 * _1M];
	}

}

控制台输出为:

从控制台输出的信息,可以看出,当第一个对象b1创建了一个7M对象时,首先会考虑到新生代的eden区,如果空间够,就会放置在此,可以看出上面的eden差不多刚好放下7M的对象,使用率占了99%。
当下面的b2创建时,发现eden区域不够了,这是,Java虚拟机就会把对象放置到老年代区域中,从上面可以看到,PSOldGen使用率为70%。

故,从这个实验,可以得出结论,对象优先在eden区存放,如果eden区域空间不够,就是放置在老年代区域中。如果两个都不够,将报错:java.lang.OutOfMemoryError: Java heap space

下面,咱们來验证长期存活的对象,将进入老年代。

什么才算是长期存货的对象呢?在Java虚拟机中,给每个对象定义了一个对象年龄计数器,如果对象在eden出生并经过GC后仍然存活,并能被Survivor容纳,被移动到Survivor空间中,那么它的年龄就加1.当年龄到一定程度(默认为15岁)时,就会被移到老年代中。其中,晋升年龄的年龄,可以通过 -XX:MaxTenuringThreshold 來设置,下面,我们通过设置它为1,來做这个实验。如下代码所示:

/**
 * 以下参数为运行设置中的VM Arguments参数
 * -verbose:gc
 * -XX:+PrintGCDetails
 * -Xms20M 
 * -Xmx20M
 * -Xmn10M
 * -XX:MaxTenuringThreshold=1
 * @author qianl
 *
 */
public class Test {

	public static int _1M = 1024 * 1024;

	
	public static void main(String[] args) throws InterruptedException {
		byte[] b1,b2;
		b1 = new byte[2 * _1M];
		b2 = new byte[2 * _1M];
		b2 = null;
		System.gc(); //使b2其经过一次GC操作
		b2 = new byte[2 * _1M]; //年龄长为1,将近入老年代区域
	}

}

控制台输出:

上述第一句Full GC...说明经过一次GC操作。
由控制台的信息,可以看到PSOldGen使用了2164K,这是由于我们将年龄判断值设为1,上面程序中,b2经过我们手动操作,年龄变为了1,于是移动到了老年代中。大家可以自己将-XX:MaxTenuringThreshold设置大些,或者不设置,看PSOldGen区域会不会被使用,本人已做过实验,证明是正确的,大家可以自己做一下。

实际上,还有一种情况,Java虚拟机会动态判定对象年龄。
如果在Survivor空间中,相同年龄的所有对象大小总和,大于Survivor空间的一半,大于或等于这个年龄的对象,就可以直接进入老年代。这个,大家也可以自行下去做下实验验证。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值