5

我有一个关于 Java 内存消耗的有趣问题。我有一个调用我的 Java 应用程序的本机 C++ 应用程序。

该应用程序基本上会进行一些语言翻译\解析一些 XML 并响应网络请求。应用程序的大部分状态不必保留,因此它充满了接受字符串参数并返回字符串结果的方法。

随着时间的推移,此应用程序继续占用越来越多的内存,并且有一段时间它开始占用接近 2 GB 的内存,这使我们怀疑某些 Hashtable 或静态变量的某处存在泄漏。经过仔细检查,我们没有发现任何泄漏。比较一段时间内的堆转储,显示 char[] 和 String 对象占用大量内存。

然而,当我们检查这些 char[]、字符串时,我们发现它们没有 GC 根,这意味着它们不应该是泄漏的原因。由于它们是堆的一部分,这意味着它们正在等待垃圾收集。在使用了各种工具 MAT\VisualVM\JHat 并浏览了很多这样的对象后,我使用了 yourkit 的试用版。Yourkit 直接给出数据说 96% 的 char[] 和 String 是不可达的。这意味着在进行转储时,堆中 96% 的字符串都在等待垃圾收集。

我知道 GC 运行很少,但是当您通过 VisualVM 检查时,您实际上可以看到它正在运行 :-( 而不是堆上一直有这么多未使用的对象。

IMO 这个应用程序永远不应该占用超过 400-500 MB 的内存,这是它在前 24 小时内停留的地方,但它会继续增加堆:-(

我正在运行 Java 1.6.0-25。

请注意您的工具包中的屏幕截图

谢谢你的帮助。

4

5 回答 5

7

当您认为 Java 确实/应该 GC 时,它不会 GC :-) GC 是一个太复杂的话题,如果不花几周时间真正深入研究细节,就无法理解正在发生的事情。因此,如果您看到无法解释的行为,那并不意味着它已损坏。

你看到的可能有几个原因:

  1. 您正在将一个巨大的字符串加载到内存中并保留对子字符串的引用。这可以将整个字符串保留在内存中(Java 并不总是为子字符串分配一个新的 char 数组 - 因为字符串是不可变的,它只是重用原始 char 数组并记住偏移量和长度)。

  2. 到目前为止没有触发 GC。一些 C++ 开发人员认为 GC 是“邪恶的”(任何你不理解的东西都一定是邪恶的,对吧?),所以他们将 Java 配置为不运行它,除非绝对必要。这意味着 VM 会吃掉内存,直到达到最大值,然后它会执行一次巨大的 GC 运行。

  3. build 25 已经很老了。尝试更新到最新的 Java 版本(我认为是 33)。GC 是 VM 中测试最好的部分之一,但它确实存在错误。也许你打了一个。

  4. 除非您看到 OutOfMemoryException,否则您没有泄漏。我们有一个应用程序可以吃掉你给它的所有堆。如果它获得 16GB 的 RAM(“只是为了安全”),它将使用整个 16GB,因为我们缓存了我们所能缓存的。您永远不会看到内存不足,因为缓存会根据需要缩小,但系统管理员经常会惊慌失措“哦上帝!哦上帝!它的内存不足了” PANIK不,不是。除非 Java 告诉您,否则它不会耗尽内存。它只是有效地使用它。

  5. 使用命令行选项调整 GC 是破坏它的最佳方法之一。数百人比你更了解这个主题,他们将花费数年时间来提高 GC 的效率。你认为你可以做得更好吗?祝你好运。-> 摆脱任何“神奇”的命令行选项和调用System.gc(),您的问题可能会消失。

于 2012-08-02T07:50:51.243 回答
2
String reallyLongString = "this is a really long String";
String tinyString = reallyLongString.substring(2, 3);
reallyLongString = null

The JVM can't collect the memory allocated for the long string in the above case, since there's a reference to part of it. If you're doing stuff with Strings and you're suffering from memory issues, this might be the cause of your grief.

use tinyString = new String(reallyLongString.substring(2, 3); instead.

于 2012-08-02T07:45:58.310 回答
2

可能根本没有泄漏 - 如果Strings 可以到达,就会发生泄漏。如果您已为应用程序分配了多达 2GB 的空间,那么垃圾收集器没有理由开始释放内存,直到您接近该限制。如果您不希望它占用超过 500MB,则-Xmx 512m在启动 JVM 时通过。

您还可以尝试调整垃圾收集器以更早地开始清理。

于 2012-08-02T07:51:46.543 回答
2

尝试将堆大小减小到 500 兆字节,看看软件是否会开始垃圾收集或死机。Java 对使用分配给它的内存并不太挑剔。您还可以研究 GC 调整选项,这将使 GC 在清理东西时更加谨慎。

于 2012-08-02T07:40:50.657 回答
0

首先,不要再担心那些字符串和 char[]。在我分析过的几乎每个 java 应用程序中,它们都位于内存使用者列表的顶部。在几乎所有这些 java 应用程序中,它们都是真正的问题。

如果您还没有收到 OutOfMemoryError,但确实担心 2GB 对于您的 java 进程来说太多了,那么请尝试减少您传递给它的 Xmx 值。如果它在 512m 或 1g 上运行良好且良好,那么问题就解决了,不是吗?

如果您获得 OOM,那么您可以尝试的另一种选择是将Plumbr与您的 java 进程一起使用。它是内存泄漏发现工具,如果确实存在内存泄漏,它可以帮助您。

于 2012-08-03T06:36:47.070 回答