JVM系列十五(对象分配注意项).
减少分配率
这个几乎不用解释,减少了内存的使用量,自然就减少 GC 回收时的压力,同时降低了内存碎片与 CPU 的使用量。在设计对象时,应仔细检查并问自己:
- 我真的需要这个对象吗?
- 这个字段是我需要的吗?
- 我能减少数组的尺寸吗?
- 这些对象,是否只有在极少数情况下,或者只有初始化的时候才用到?
- 我是否分配了大量内存,但实际只使用其中很小的一部分?
- 我可以从其它地方拿到相关数据?
缩短对象的生命周期
对于垃圾回收的高性能编程有一个基本规则,事实上也是代码设计的指导规则。
要收集的对象要么在新生代,要么不存在。
Collect objects in gen 0 or not at all.
尽量让一个对象拥有极短的生命周期,在 Minor GC 的时候就能立即被回收了;或者就应该让对象快速晋升到老年代,永远保持对长生命周期对象的引用,通常,这也意味着对象可重复使用,尤其在大对象堆中的对象。
一个对象的作用范围越短,在下一个 GC 出现时,它被晋升到老年代的机会就越小。
因此,要确保对象尽可能早的退出作用域,对于局部变量,可以在最后一次使用后,甚至在方法结束前将其生命周期结束。你可一个用 {} 将代码包括起来,这不会对你的运行产生影响,但编译器会认为在这个范围的对象已经完成了它的生命周期,不再被使用了。如果需要调用对象的方法,尽量减少第一次和最后一次的时间间隔,以便 GC 尽早的回收对象。
如果对象关联(引用)了一些会长时间保持的对象,则需要解除它们的引用关系。你可能会因此有更多的空值检查(null判断),这可能会让代码变得更复杂。
降低对象层次的深度/减少对象之间的引用
JVM 是通过 可达性分析算法 来判断对象是否存活的,如果对象的层次很深,或者大量的引用了其他对象,JVM 在判断存活的时候就会花很多时间在遍历对象上,这是 GC 造成长时间的一个原因。
另外一个问题是,如果无法轻松的确定对象有多少引用关系,那么就无法准确的预测对象的生命周期。减少这种复杂度是相当有必要的,它不但可以让代码更健壮,同时也方便调试以及获得更好的性能。
另外,还要注意不同代对象之间的引用也会导致 GC 的效率低下,特别是旧对象对新对象的引用。例如,如果老年代对象在新生代对象里有引用关系,那么每次发生新生代的 GC 时,也需要扫描部分老年代对象,看看他们是否仍然保持到新生代对象的引用上。虽然这不是一次完整的 GC,但它仍然是不要的工作,应该尽量避免这种情况。
避免大对象
JVM 对于大对象的处理逻辑是直接在老年代进行分配,这样做的目的是避免在 Eden 区和及两个 Survivor 区之间发生大量的内存复制。
一般我们代码中常见的大对象是指那种很长的字符串以及数组,写程序的时候应当避免,经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的内存空间来“安置”它们。