Java 8 会解决 PermGen OutOfMemoryError 问题吗?

  • Fabian Lange
  • 臧秀涛

2013 年 3 月 11 日

话题:JavaJVM语言 & 开发

Java 8会解决PermGen OutOfMemoryError问题吗

Oracle 正在合并 HotSpot 和 JRockit 的代码库,作为该项目的一部分,Oracle 宣布他们会将 PermGen 从 Java 8 的 HotSpot JVM 中移除。然而很多人都认为这意味着所有的 PermGen 错误也将消失不见。因为现在可以通过Java 8 Early Access构建版本来检查移除 PermGen 的效果,所以是时候看看是不是所有 PermGen 问题都已解决了。

PermGen是什么?

Jon Masamitsu(JVM 开发者,现就职于 Oracle)曾于 2006 年在其博客上解释过永久代(Permanent Generation)的用途:永久代包含了类相关的信息,包括字节码、名字和 JIT 等信息。它被保存在一个独立的空间中,因为它通常是静态的,垃圾收集改为垃圾回收。

PermGen带来的问题

很多开发者都在其系统中见过“java.lang.OutOfMemoryError: PermGen space”这一问题。这往往是由类加载器相关的内存泄漏以及新类加载器的创建导致的,通常出现于代码热部署时。相对于正式产品,该问题在开发机上出现的频率更高,正是这个原因。当它在产品中出现时,开发者可以拿到生成的堆转储文件,并利用像Eclipse Memory Analyzer Toolkit这样的工具来寻找应该卸载却没被卸载的类加载器。除非通过特定配置阻止,PermGen 也是会进行垃圾回收的。然而,在出现内存泄漏时,就没什么可回收的了。在产品中最常见的“问题”是 64MB 这个默认值太低了。常用的解决方法是将其设置为 256MB。

Java 8改变了什么

Jon 在HotSpot 开发邮件列表中解释了 Java 8 将发生的变化:Java 8 中再也没有 PermGen 了。其中的某些部分,如被 intern 的字符串,在 Java 7 中已经移到了普通堆里。其余结构在 Java 8 中会被移到称作“Metaspace”的本机内存区中,该区域在默认情况下会自动生长,也会被垃圾回收。它有两个标记:MetaspaceSize 和 MaxMetaspaceSize。

应 InfoQ 的要求,Jon Masamitsu 解释了其背后的设计目标:

移除了 PermGen,用户就无需考虑如何正确设置其大小了,这是我们的一个目标。

如果知道应用程序的类数据需要更多空间,可以把 MetaspaceSize 设置的比默认值大些。 这能减少一些启动时的 GC 次数。不过没必要这么做。除非你想尽量减少 GC 次数,否则我不建议这么做。

如果想限制类数据所占空间的大小,可以设置 MaxMetaspaceSize。如果怀疑出现类加载器泄漏,并且希望应用在耗尽太多本机内存前停止,应该设置它。还有一种使用场合,那就是在一个服务器上运行了多个应用,而且用户希望限制每个应用所占的类空间大小。

因此设置 MetaspaceSize 会潜在影响头几次垃圾回收,大部分情况下并不重要。这也反映出类似旧式 PermSize 标记的用途。

在设置了 MaxMetaspaceSize 的情况下,该空间的内存仍然会耗尽,进而引发“java.lang.OutOfMemoryError: Metadata space”错误。因为类加载器的泄漏仍然存在,而通常 Java 又不希望无限制地消耗本机内存,因此设置一个类似于 MaxPermSize 的限制看起来也是合理的。与 PermGen 类似,verbose: GC 日志会打印 Metaspace 当前的内存消耗情况。使用命令行标记 PermSize 或 MaxPermSize 会导致一个警告,通知用户切换为 Metaspace 标记。

结论

因为 Metaspace 和 PermGen 的理念几乎是相同的,管理员在将 Java 7 升级到 Java 8 时,只需执行 sed 'e/Perm/Metaspace/g'就能修改相应标记。

总的来说,变化看起来平淡无奇。大多数情况下,只是名字变了一下。默认情况下不限制 Metaspace,以避免所选的默认值太小,但为了保证系统的稳定性又需要设置一下最大值。幸运的是我们可以复用 PermSize 和 MaxPermSize 的配置——几乎每个人都用过——只需要改一下标记即可。遗憾的是,从托管 Java 堆迁移到本机内存意味着堆转储文件中有价值的故障诊断信息少了许多,这正是 Kirk Pepperdine所担忧的

最后,类加载器泄漏的问题和以前一样,仍然会出现。

译者补记

英文站原新闻下有些评论,读者 Ronald Miura 并不同意本文的结论,他不客气地评论道:

如果将类信息加载到有限内存区域中,最后会出现 OutOfMemoryError。如果不使用有限的区域,你会得到“不稳定的系统”。

还“平淡无奇”,你想要什么,让 JVM 自动修正你的内存泄漏 bug 吗?

来自 Oracle 的 Cameron Purdy 倒是很平和:

还是会好一些的。之前不管是不是需要,JVM 都会吃掉那块空间……如果设置得太小,JVM 会死掉;如果设置得太大,这块内存就被 JVM 浪费了。理论上说,现在你完全可以不关注这个,因为 JVM 会在运行时自动调校为“合适的大小”。

peter lin 对 Ronald Miura 的意见表示赞同,他补充说:

过去我使用并测试过 JRockit,由 JVM 来管理 PermGen,开发者就轻松多了。我都记不清 tomcat 用户在邮件列表中问过我多少次相关的问题了。

Ronald Miura 后来又补充了一下自己的意见:

……我认为这种改变是巨大的进步。

我很反感本文的结论,把这种改动诋毁为“平淡无奇”。如果你期望的是无解问题(自动修复你的 bug)的神奇方案,那才算“平淡无奇”吧。

亲爱的 InfoQ 读者,你怎样看待这个问题?欢迎参与讨论。

查看英文原文Will Java 8 solve PermGen OutOfMemoryError?

JavaJVM语言 & 开发