您的位置:首页 > 编程语言 > Java开发

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

2013-02-24 21:03 204 查看
    先看下面这个小程序

/**
* 这个小程序作用是创建一个大约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 spac
4000
e和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空间的一半,大于或等于这个年龄的对象,就可以直接进入老年代。这个,大家也可以自行下去做下实验验证。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: