对象内存分配过程
对象内存分配流程图
3.1 栈上分配
Java对象都是在堆上进行分配,在对象没有被引用时,依赖GC回收内存。当对象数量过多,便会给GC带了较大压力,影响应用性能。JVM提供了栈上分配机制,用于减少临时对象在堆上的分配数量。
JVM通过逃逸分析确定该对象不会被外部访问。如果不会逃逸,可以优化对象内存分配位置,通过标量替换优先分配在栈上(栈上分配),这样该对象所占用的内存空间就可以随栈帧出栈而销毁,就减轻了垃圾回收的压力。`。
对象逃逸分析:就是分析对象动态作用域。JDK7之后默认开启逃逸分析-XX:+DoEscapeAnalysis
标量替换:通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM不会创建该对象,而是将该对象成员变量分解若干个被这个方法使用的成员变量所代替,这些代替的成员变量在栈帧或寄存器上分配空间,这样就不会因为没有一大块连续空间导致对象内存不够分配。开启标量替换参数(-XX:+EliminateAllocations
),JDK7之后默认开启。
结论:栈上分配依赖于逃逸分析和标量替换
// -Xmx15m -Xms15m -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
public class AllotOnStack {
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
alloc();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
private static void alloc() {
User user = new User();
user.setId(1);
user.setName("zhuge");
}
}
3.2 对象在Eden区分配
大多数情况下,对象在新生代中 Eden 区分配。虽然可能经过TLAB,但TLAB也是Eden区域。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
- Minor GC/Young GC:指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。
- Major GC/Full GC:一般会回收老年代 ,年轻代,方法区的垃圾,Major GC的速度一般会比Minor GC的慢10倍以上。
Eden与Survivor区默认8:1:1
大量的对象被分配在eden区,eden区满了后会触发minor gc,可能会有99%以上的对象成为垃圾被回收掉,剩余存活的对象会被挪到为空的那块survivor区,下一次eden区满了后又会触发minor gc,把eden区和survivor区垃圾对象回收,把剩余存活的对象一次性挪动到另外一块为空的survivor区,因为新生代的对象都是朝生夕死的,存活时间很短,所以JVM默认的8:1:1的比例是很合适的,让eden区尽量的大,survivor区够用即可,
JVM默认有这个参数-XX:+UseAdaptiveSizePolicy
(默认开启),会导致这个8:1:1比例自动变化。
3.3 大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。JVM参数 -XX:PretenureSizeThreshold
可以设置大对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在 Serial 和ParNew两个收集器下有效。
比如设置JVM参数:-XX:PretenureSizeThreshold=1000000
(单位是字节) -XX:+UseSerialGC
,再执行下上面的第一个程序会发现大对象直接进了老年代
为什么要这样呢?
为了避免GC时,大对象在Eden区以及两个Survior区多次复制操作。
3.4 长期存活的对象将进入老年代
既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个分代年龄(Age)计数器。
如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1。对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁,CMS收集器默认6岁,不同的垃圾收集器会略微有点不同),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold
来设置。
3.5 对象动态年龄判断
当前放对象的Survivor区域里(其中一块区域,放对象的那块s区),一批对象的总大小大于这块Survivor区域内存大小的50%(-XX:TargetSurvivorRatio
可以指定),那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了,例如Survivor区域里现在有一批对象,年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代。这个规则其实是希望那些可能是长期存活的对象,尽早进入老年代。对象动态年龄判断机制一般是在minor gc之后触发的。
3.6 老年代空间分配担保机制
空间担保指的是老年代为新生代对象晋升做内存担保。
在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间或者历次晋升到老年代的对象的平均大小,如果大于,则此次Minor GC是安全的,进行MinorGC,否则进行FullGC.
JDK 6 Update 24之后,-XX:-HandlePromotionFailure
不再影响JVM的空间分配担保策略。