1.选择垃圾收集器的权衡
衡量垃圾收集器的重要指标是:内存占用、吞吐量、和延迟,三者构成了一个不可能三角,也就是说不能三者兼得,通常一款优秀的收集器最多可以同时达成其中的两项。
而一般垃圾收集器的选择会从一下三点出发:
①应用程序的主要关注点,需要尽快算出结果则需要关注吞吐量,希望短的停顿时间则关注低延迟,客户端应用或者嵌入式应用则更关注内存占用。
②运行应用的基础设施情况,硬件规格、处理器数量、操作系统等等。
③使用JDK的发行商和版本号。
2.内存分配与回收策略
新生代GC(Minor GC):指发生在新生代的垃圾回收动作,Java对象生存周期短,Minor GC非常频繁,但速度也快。
老年代GC(Major GC/Full GC):发生在老年代的垃圾回收,出现Major GC常常会伴随至少一次的Minor GC(Parallel Scavenge中有直接进行Major GC的选择),Major GC比Minor GC慢十倍以上。
对象优先在Eden分配:
大多数情况下对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
大对象直接进入老年代:
大对象指的是需要大量连续空间的对象,经常出现大对象容易导致内存还有不少空间时就会触发垃圾收集。虚拟机提供-XX:PretenureSizeThreshold参数,将大于这个设置值的对象直接在老年代分配。这样做的目的就是避免在Eden区及两个Survivor区之间发生大量的内存复制。
长期存活的对象进入老年代:
虚拟机定义了一个对象年龄计数器,如果对象在Eden中产生且能经过一次Minor GC进入Survivor区,将对象年龄设为1岁,之后若在Survivor区中每熬过一次Minor GC,则年龄就增加1岁,当年龄达到一定阈值时,会晋升到老年代。阈值可以通过参数-XX: MaxTenuringThreshold设置,默认值为15。
动态对象年龄判定:
一半规则,对于不同情况并不是所有对象都要到达阈值后才能从Survivor区中晋升到老年代,当Survivor中相同年龄的对象大小总和大于Survivor空间的一半时,年龄大于或者等于该年龄的对象可以直接进入老年代。
空间分配担保:
如果大量对象在Minor GC后仍然存活,这时就需要老年代进行空间分配担保,把Survivor区无法容纳的对象直接进入老年代。需要进行这样的担保,就需要虚拟机在Minor GC之前检查老年代最大可用的连续空间是否大于新生代所有对象总空间。
如果条件成立则说明此次Minor G安全,如果不成立,虚拟机会通过参数- XX:HandlePromotionFailure查看是否允许担保失败。如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大 于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者-XX: HandlePromotionFailure设置不允许冒险,那这时就要改为进行一次Full GC。
其中有风险的Minor GC原因是因为,取平均值比较是一种动态概率的手段,若某次Minor GC后存活的对象激增使得平均值升高,也会导致担保失败,这时还是要进行一次Full GC.