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更加了解。后边会更新更详细的内存分配机制与JVM原理,请关注。