内存分配与回收策略
Java技术体系中的自动内存管理最终可以归结为自动化地解决两个问题:给对象分配内存和回收分配给对象的内存。关于内存回收这一点,我们在Java垃圾收集机制中详细介绍了各种回收算法以及JVM中常见的收集器。接下来我们主要看看JVM是如何给对象分配内存的。
对象的内存分配,往大的方向上说就是在堆上的分配(但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配,这是JIT即时编译器的优化,我们不做介绍),对象主要分配在新生代(Eden和Survivor)的Eden区上。少数情况下也可能会直接分配在老年代(Tenured)中,其分配的细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数设置。接下来我们将通过例子讲解几条最普遍的内存分配规则。
对象优先在Eden分配大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够的空间进行内存分配时,虚拟机将发起一次Minor GC(新生代内存回收)。其中虚拟机通过参数 -XX:+PrintGCDetails打印垃圾收集器日志参数,告诉虚拟机在发生垃圾收集行为时打印内存回收日志。我们执行代码如下,其中虚拟机参数设置为-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8。
public class Main {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* -Xms20M 设置堆大小为20M -Xmx20M 避免堆自动扩展
-Xmn10M 设置年轻代大小
* -XX:+PrintGCDetails 打印日志信息
* -XX:SurvivorRatio=8 设置Eden和Survivor大小比值
*/
// TODO Auto-generated method stub
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[6 * _1MB]; //新生代需要Minor GC,Full GC直接将其分配进OldGen
allocation4 = new byte[4 * _1MB]; //新生代分配内存
}
}
//打印日志如下
1、[GC-- [PSYoungGen: 4932K->4932K(9216K)] 11076K->13124K(19456K), 0.0050819 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2、[Full GC [PSYoungGen: 4932K->2511K(9216K)] [ParOldGen: 8192K->8192K(10240K)] 13124K->10703K(19456K) [PSPermGen: 2551K->2550K(21504K)], 0.0123169 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
3、Heap
4、PSYoungGen total 9216K, used 6774K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
5、eden space 8192K, 82% used [0x00000000ff600000,0x00000000ffc9d840,0x00000000ffe00000)
6、from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
7、to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
8、ParOldGen total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
9、object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400020,0x00000000ff600000)
10、PSPermGen total 21504K, used 2557K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
11、object space 21504K, 11% used [0x00000000f9a00000,0x00000000f9c7f688,0x00000000faf00000)
为了解释方便,给日志加上了行号,首先第1行,GC说明发生了垃圾回收。其中[PSYoungGen: 4932K->4932K(9216K)]表示新生代应用Parallel Scavenger收集器,垃圾收集前内存占用4932K,垃圾收集后内存占用4932K,因为allocation1和allocation2对象都还存活;该区域总内存为9216K(9M)。其中11076K->13124K(19456K)表示Java堆内存回收前后占用内存和总内存。其中0.0050819 secs表示执行内存回收时间。其中[Times: user=0.00 sys=0.00, real=0.00 secs],user表示用户态消耗的CPU时间,sys表示内核态消耗的CPU时间,real表示操作开始到结束所经过的墙钟时间。同理第2行表示为:新生代执行了Full GC,内存变化为由4932K变为2511K,说明有2M内存进入老年代;老年代采用Parallel Old收集器,新创建的allocation3同样进入老年代,可以看到老年代内存占用为8192K(8M);永久代(PermGen)内存没有太大变化。第3-11行是程序执行完内存的情况新生代内存总共9M,内存占用6774K(最后allocation4直接分配在新生代中),新生代Eden区8192K,占用82%;from和to区域1024K,占用0%;老年代内存总共10M,占用8M(2M+6M)。其中永久代内存总共21M(21504K),占用2557K。
大对象直接进入老年代