内存分配策略
-
优先分配到eden
大多数情况下,对象在新生代 Eden 区中分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC 。
Minor GC(新生代 GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 发生的非常频繁,一般回收速度也比较快。
Major GC(老年代 GC):指发生在老年代的 GC,出现了 Major GC ,经常会伴随至少一次的 Minor GC(不绝对)。Major GC 的速度一般会比 Minor GC 慢 10 倍以上。
-
大对象直接分配到老年代
所谓的大对象是指,需要大量连续内存空间的 Java 对象。最典型的大对象就是很长的字符串和数组,经常出现大对象会导致内存还有不少空间时就提前出发垃圾收集以获取足够的连续空间。
虚拟机提供了一个
-XX:PretenureSizeThreshold
参数,令大于这个设置值的对象直接在老年代分配。这样做的目的是避免在 Eden 区及两个 Survivor 区之间发生大量的内存复制。 -
长期存活对象分配到老年代
即然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能够识别哪些对象应该放在新生代,哪些对象应该放在老年代。为了做到这点,虚拟机给每个对象定义了一个对象年龄计数器。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并且对象年龄设为 1 。对象在 Survivor 中每“熬过”一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度时(默认为 15 岁),就将会被晋升到老年代中。对象进入老年代的阈值,可以通过参数 -XX:MaxTenuringThreshold 设置。
-
动态对象年龄判断
为了能更好地适应不同程序中的内存状况,虚拟机并不是永远地要求对象的年龄必须达到
MaxTenuringThreshold
才能晋升老年代。如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold
中要求的年龄。 -
空间分配担保
在发生 Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么 Minor GC 可以确保是安全的。如果不成立,则虚拟机会查看
HandlePromotionFailure
设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC, 尽管这次 Minor GC 是有风险的;如果小于,或者HandlePromotionFailure
设置不允许冒险,那这时候要改为进行一次 Full GC。
逃逸分析与栈上分配
逃逸分析:分析对象的作用域(受访问权限仅在在方法内) —》将未逃逸对方分配到栈中(进栈,出栈),不需要进行垃圾回收
package MyDemo.work;
public class StackAllocation {
public StackAllocation obj;
//方法返回StackAllocation对象,发生逃逸
public StackAllocation getInstance() {
return obj == null ? new StackAllocation() : obj;
}
//为成员属性赋值,发生逃逸
public void setObj(){
this.obj = new StackAllocation();
}
//对象的作用域仅在当前方法中有效,没有发生逃逸
public void useStackAllocation(){
StackAllocation s = new StackAllocation();
}
//引用成员变量的值,发生逃逸
public void useStackAllocation2(){
StackAllocation s = getInstance();
}
}
避免在循环体中创建对象,即使该对象占用内存空间不大。
尽量及时使对象符合垃圾回收标准。
不要采用过深的继承层次。
访问本地变量优于访问类中的变量。