Java 的内存大小与垃圾回收简述

一些 Java 程序在高负荷时性能会出现显著下降。一个可能的原因是没有设定恰当的内存大小(memory sizing),导致垃圾回收(Garbage collection,下称 GC)频繁执行。即使 CPU 满负荷运作,性能依然较差。

JVM 的内存分 Java 专用区域与 C Heap 两部分,其中前者又分为维护 Java 对象的 Java Heap 区域与维护类信息的 Permanent 区域。一些采用了分代收集机制(Generational Garbage Collection,或称 Generation Scavenging)的 GC 系统还将 Java Heap 进一步细分为 Eden、Survivor 与 Old 区域。Eden 维护生成的对象,GC 时未被回收的对象将被移入 Survivor,长期存在的对象进一步进入 Old 区域。

对于分代收集,GC 可分为 Full GC 与 Copy GC,其中,Copy GC 仅针对 New 区域(Eden+Survivor),Full GC 则针对所有区域。为防止 GC 过于频繁,我们需要设置合适的内存大小。常见的情况有以下这些:

Copy GC

  • Eden 区域空间不足

Full GC

  • Eden 区域空间不足时,Old 区域的可用内存大小不足以容纳 New 区域的已使用内存
  • New 区域与 Old 区域的可用内存空间不足
  • GC 后 Old 区域的剩余空间过低
  • GC 后 Old 区域的空间增加
  • Permanent 区域的可用空间不足

在程序执行过程中,Permanent 区域很少发生变化,因此我们因主要考虑 Full GC 的前两种情况。未避免 Old 区域的空间增加,建议将 Java Heap 的初始值与最大值设为同一值。

通常,Full GC 更为耗时,因此我们应主要考虑减少 Full GC,令 Copy GC 可以解决大部分回收。为此,Old 区域的内存大小是一个关键。

Old 区域的内存大小应与 Java 程序占用内存、Java 服务器占用内存,及 New 区域的内存大小,之和,相当。

举例来说,对于 Web 程序,事务处理占用的内存可以被立即释放,会话占用的内存则需要长期保存。因此后者应归为 Java 程序占用内存。

New 区域的内存大小也需要专门考虑,尤其是 Survivor 区域的大小十分重要。如果 Survivor 区域的内存大小不足,对象就需要被移动至 Old 区域。如果这些对象的生存周期不够长,就会进一步引发 Full GC。增加 Survivor 区域可以缓解该问题。然而,如果 Survivor 区域过大,Copy GC 的处理时间就会增加。因此,应当设置一个阀值,将经历 Copy GC 次数超过该值的对象移植 Old 区域。应尽可能避免 Survivor 区域已满,对象从 Eden 区域直接被移动至 Ole 区域的情况。为此,需要在 Copy GC 发生后检查 Survivor 区域是否被完全占用,以及移动至 Old 区域的对象年龄(生存次数)是否合适。如有可能,应为 Survivor 区域分配较大的内存大小,以适应需求。具体的内存大小估算方式,可以查找其他相关文章。

Eden 区域的内存大小将直接影响 Copy GC 的频率。通常,每发生 10-20 次 Copy GC 后执行一次 Full GC 较为合适。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注