JVM内存分配与回收分析

来源:互联网 发布:深圳淘宝达人直播机构 编辑:程序博客网 时间:2024/05/16 19:05

 java虚拟机的自动化内存可以归结为自动化解决了两个问题,一个是内存分配,一个是内存回收。了解虚拟机的分配与回收机制,能让我们对项目的把控更加有力,尤其是对性能调优时,

各个参数的设置可能会有意想不到的效果。本文结合事例分析各种场景的回收。

  堆内存区域(不包括永久代)种类:1,eden space(属于新生代-new generation)

          2,survivor space(属于新生代-new generation)

          3,tenured generation(年老代)

  首先解释下各参数的含义。我们可以通过run -> run configuration 中选择argument 中的VM arguments来输入参数调节个参数值。

    -Xms20m : 代表给堆分配20m的初始内存

    -Xmx50m : 代表堆的最大分配内存为50m

    -Xmn10m : 代表给新生代(new generation)分配10m的内存

    -Xss128k:每个线程最大堆栈内存

       -XX:PermSize=10M : 给永久代分配10M内存

    -XX:MaxPermSize=100M:永久代最大内存为100M

           -XX:MaxDirectMemorySize=10M:直接内存最大为10M

           -XX:+PrintGCDetails:控制台输出GC信息

    -XX:SurvivorRatio=8:新生代(new generation)中eden区与survivor区分配的比例为8:1

     -XX:PretenureSizeThreshold=2m:对象内存大小超过这个直接放入年老区(tenured generation)

     -XX:MaxTenuringThreshold=3:代表对象在三次后即第四次GC会进入年老区(tenured generation)

 

  分配策略一:对象优先在eden中分配 

    配置信息:-Xmx20m -Xms20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8

    代码:

复制代码
public class Allocation {        public static final int _1MB = 1024*1024;    public static void main(String[]args)    {        byte[] allocation1,allocation2,allocation3,allocation4,allocation5;                allocation1 = new byte[4*_1MB];    }}
复制代码

  执行结果:

复制代码
Heap def new generation   total 9216K, used 4831K [0x331d0000, 0x33bd0000, 0x33bd0000)  eden space 8192K,  58% used [0x331d0000, 0x33687fd0, 0x339d0000)  from space 1024K,   0% used [0x339d0000, 0x339d0000, 0x33ad0000)  to   space 1024K,   0% used [0x33ad0000, 0x33ad0000, 0x33bd0000) tenured generation   total 10240K, used 0K [0x33bd0000, 0x345d0000, 0x345d0000)   the space 10240K,   0% used [0x33bd0000, 0x33bd0000, 0x33bd0200, 0x345d0000) compacting perm gen  total 12288K, used 145K [0x345d0000, 0x351d0000, 0x385d0000)   the space 12288K,   1% used [0x345d0000, 0x345f4468, 0x345f4600, 0x351d0000)    ro space 10240K,  42% used [0x385d0000, 0x38a14240, 0x38a14400, 0x38fd0000)    rw space 12288K,  54% used [0x38fd0000, 0x39654d58, 0x39654e00, 0x39bd0000)
复制代码

  我们逐行分析,第二行:显示了,我们给新生代分配了9216K内存(这个由于XX:SurvivorRatio参数不同,会有小幅变动,总体接近10M)

       第三行:显示了eden代分配了8912k,并且58%被使用,这个就是程序中新加入的对象放到了新生代中,刚好是4M

       第四行,第五行,两个值默认是一样,是指survivor区的大小,8192:1024刚好是XX:SurvivorRatio=8的8:1

                   第六行:年老代,10240k,总共分配了20M的堆,新生代占了10M,那么年老代就是10M

       后面的则是永久代信息,这边暂不讨论。

  从上面的代码,执行结果,以及分析中,我们能看出最初的对象是分配在eden space中的。

  当然如果eden满了,则会分配到suvivor space 和tenured generation 中,如:

复制代码
public class Allocation {        public static final int _1MB = 1024*1024;    public static void main(String[]args)    {        byte[] allocation1,allocation2,allocation3,allocation4,allocation5;                allocation1 = new byte[2*_1MB];        allocation2 = new byte[2*_1MB];        allocation3 = new byte[2*_1MB];        //第四次分配不足,出发gc        allocation4 = new byte[4*_1MB];    }}
复制代码

执行结果:

复制代码
[GC [DefNew: 6716K->378K(9216K), 0.0038688 secs] 6716K->6522K(19456K), 0.0038989 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation   total 9216K, used 4802K [0x331d0000, 0x33bd0000, 0x33bd0000)  eden space 8192K,  54% used [0x331d0000, 0x33621fa0, 0x339d0000)  from space 1024K,  36% used [0x33ad0000, 0x33b2e940, 0x33bd0000)  to   space 1024K,   0% used [0x339d0000, 0x339d0000, 0x33ad0000) tenured generation   total 10240K, used 6144K [0x33bd0000, 0x345d0000, 0x345d0000)   the space 10240K,  60% used [0x33bd0000, 0x341d0030, 0x341d0200, 0x345d0000) compacting perm gen  total 12288K, used 145K [0x345d0000, 0x351d0000, 0x385d0000)   the space 12288K,   1% used [0x345d0000, 0x345f44a0, 0x345f4600, 0x351d0000)    ro space 10240K,  42% used [0x385d0000, 0x38a14240, 0x38a14400, 0x38fd0000)    rw space 12288K,  54% used [0x38fd0000, 0x39654d58, 0x39654e00, 0x39bd0000)
复制代码

  经历了一次GC,信息中显示了各个内存区域的使用情况

 

  分配策略二:大对象直接进入老年代

    配置信息:-Xmx20m -Xms20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3m

    代码:

复制代码
public class Allocation {        public static final int _1MB = 1024*1024;    public static void main(String[]args)    {        byte[] allocation1,allocation2,allocation3,allocation4,allocation5;                allocation4 = new byte[4*_1MB];    }}
复制代码

  执行结果:

复制代码
Heap def new generation   total 9216K, used 735K [0x331d0000, 0x33bd0000, 0x33bd0000)  eden space 8192K,   8% used [0x331d0000, 0x33287fc0, 0x339d0000)  from space 1024K,   0% used [0x339d0000, 0x339d0000, 0x33ad0000)  to   space 1024K,   0% used [0x33ad0000, 0x33ad0000, 0x33bd0000) tenured generation   total 10240K, used 4096K [0x33bd0000, 0x345d0000, 0x345d0000)   the space 10240K,  40% used [0x33bd0000, 0x33fd0010, 0x33fd0200, 0x345d0000) compacting perm gen  total 12288K, used 145K [0x345d0000, 0x351d0000, 0x385d0000)   the space 12288K,   1% used [0x345d0000, 0x345f4468, 0x345f4600, 0x351d0000)    ro space 10240K,  42% used [0x385d0000, 0x38a14240, 0x38a14400, 0x38fd0000)    rw space 12288K,  54% used [0x38fd0000, 0x39654d58, 0x39654e00, 0x39bd0000)
复制代码

   从结果中可以看出来,eden space,survivor都未分配内存,而tenured generation 则分配了4096k,即4M大小超过了XX:PretenureSizeThreshold=3M限制,直接进入老年代

 

  分配策略三:长期存活的对象将进入年老代

    jvm在管理内存时,会把存活时间长的对象放入年老代,没经历一次GC存活下来的的对象,他的age加1,

    参数:-Xmx20m -Xms20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1

    代码:

复制代码
public class Allocation {        public static final int _1MB = 1024*1024;    public static void main(String[]args)    {        byte[] allocation1,allocation2,allocation3,allocation4,allocation5;                allocation1 = new byte[1*_1MB/4];        allocation2 = new byte[4*_1MB];        allocation3 = new byte[4*_1MB];        allocation3 = null;        allocation3 = new byte[4*_1MB];    }}
复制代码

    执行结果:

复制代码
[GC [DefNew: 4924K->633K(9216K), 0.0030944 secs] 4924K->4729K(19456K), 0.0031222 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC [DefNew: 4893K->0K(9216K), 0.0009591 secs] 8989K->4729K(19456K), 0.0009776 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation   total 9216K, used 4260K [0x331d0000, 0x33bd0000, 0x33bd0000)  eden space 8192K,  52% used [0x331d0000, 0x335f8fd8, 0x339d0000)  from space 1024K,   0% used [0x339d0000, 0x339d0088, 0x33ad0000)  to   space 1024K,   0% used [0x33ad0000, 0x33ad0000, 0x33bd0000) tenured generation   total 10240K, used 4729K [0x33bd0000, 0x345d0000, 0x345d0000)   the space 10240K,  46% used [0x33bd0000, 0x3406e6d0, 0x3406e800, 0x345d0000) compacting perm gen  total 12288K, used 144K [0x345d0000, 0x351d0000, 0x385d0000)   the space 12288K,   1% used [0x345d0000, 0x345f4270, 0x345f4400, 0x351d0000)    ro space 10240K,  42% used [0x385d0000, 0x38a14240, 0x38a14400, 0x38fd0000)    rw space 12288K,  54% used [0x38fd0000, 0x39654d58, 0x39654e00, 0x39bd0000)
复制代码

    从上面中,我能看到,在第二次GC中,第二行 [DefNew: 4893K->0K(9216K) 新生代全部清为0,都放到年老代中去了,因为他们的age已经为2,超过-XX:MaxTenuringThreshold=1的限制。

   当然GC的内存分配与回收策略有很多中,而且对于不同的GC回收器,回收机制也不一定一样,上面讲的三种回收机制,使用较广泛,我们可以在eclipse等ide工具中实践。通过不断的调

节与实践,会使自己对GC以及JVM更加了解。

0 0
原创粉丝点击