自动内存管理归根结底来说分为两方面: 给对象分配内存 和 回收分配给对象的内存 。本文主要介绍几条 Java虚拟机中自动内存分配及回收的主要规则 。
阅读之前,需要知道两个概念:Minor GC和Full GC
Full GC(Major GC):指发生在老年代的GC,出现了Major GC, 经常会伴随至少一次的Minor GC (但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。
大多数情况,对象优先在Eden区进行分配,当Eden区空间不够分配对象时,虚拟机将发起一次Minor GC
大对象指的是需要大量连续内存空间的Java对象(比较典型的就是很长的字符串以及数组)。对于Serial和ParNew两款收集器提供了-XX:PretenureSizeThreshold参数,令大于这个值的对象直接进入老年代。
目前jdk1.8默认的收集器Parallel Scavenge不支持此参数。如果需要这个参数,建议使用ParNew+CMS
虚拟机对每一个对象定义了一个对象年龄计数器,当对象第一次经历Minor GC时,对象从Eden区进入Survivor区,此时对象年龄记做1。 对象在Survivor区每经历一次Minor GC,年龄增加1岁。当对象达到进入老年代年龄的程度时(默认15岁),对象晋升到老年代。 通过-XX:MaxTenuringThreshold设置晋升到老年代的年龄阈值。
除了通过设置晋升老年代的年龄阈值来判定对象是否进入老年代之外, 当某个年龄的所有对象所占内存总和大于Survivor空间的一半,那么,大于等于这个年龄的对象会直接进入老年代
年轻代GC采用复制算法(当Eden和其中一个Survivor区中的存活对象复制到另一个Survivor区)进行GC回收时,当其中一个Survivor区不足以容纳存活的对象,就需要老年代提供内存空间来存放Survivor区无法容纳的多出来的对象。这就是 担保 的概念。
这时候有一个问题: 我们无法在对象回收前得知有多少对象会存活下来 。如果每次我们为了确保老年代空间能完全容纳新生代GC后幸存的对象,就需要每次都进行Full GC,但是每次都进行Full GC耗时过长而导致停顿时间增长,用户体验很不好。
针对这种情况,虚拟机采用的方法是:在垃圾回收之前,取 之前每一次回收晋升到老年代的对象容量的平均大小作为参考,老年代剩余空间大于这个值或者大于新生代对象总大小则进行Minor GC,小于这个值则进行Full GC 。虽然这样仍然会出现“担保失败”的情况,但是避免了过于频繁的Full GC。