【问题标题】:Java Heap Overflow, Forcing Garbage CollectionJava 堆溢出,强制垃圾回收
【发布时间】:2011-01-26 15:24:45
【问题描述】:

我创建了一个包含一组孩子的特里树。删除一个单词时,我将孩子设置为空,我会假设删除节点(删除是一个相对术语)。我知道 null 不会删除孩子,只是将其设置为 null,这在使用大量单词时会导致堆溢出。

在 linux 上运行 top,我可以看到我的内存使用量很快飙升到 1gb,但是如果我在删除 (Runtime.gc()) 后强制垃圾收集,内存使用量会达到 50mb 并且永远不会超过这个值。据我所知,java 默认情况下会在堆溢出发生之前运行垃圾收集,但我看不到它会发生。

【问题讨论】:

  • 你不应该在内存边缘玩,先修复你的应用程序以防止 1GB 峰值。
  • @medopal:虽然应该注意不要请求比实际需要更多的资源,但对于某些应用程序来说,从对GC 并利用这种理解来充分利用可用内存。虽然调用 Runtime.gc() 是“一般”不好的做法,但它会暴露给开发人员是有原因的。
  • @Eric,好点子,我个人的 Java 应用程序可以用于台式机到大型机(通过许多操作系统)和不同的设置,管理内存可能会变得非常复杂
  • 不要强制 java 交换。非常非常糟糕的事情发生了,它会破坏你的电脑。我实际上不得不重新启动——它太频繁地触及内存的所有部分。
  • @medopal:我怀疑绝大多数 Java 程序都符合与您类似的标准。只是说在软件开发中存在一些边缘情况,您希望能够最大限度地利用可用内存。

标签: java heap-memory


【解决方案1】:

(评论太长了)

与普遍的看法相反,您可以确实在 Java 中强制执行 GC,但这不是使用 System.gc() 完成的。真正强制 GC 的方法是使用 JVMTI 的 ForceGarbageCollection() 调用。不要再问我了,我在这里问了一个问题,没有人觉得它很有趣(没有投票),也没有人能回答它,但是 JVMTI 的 ForceGarbageCollection() 是很多 Java 程序,如 IntelliJ、NetBeans 、VisualVM、Eclipse 等确实强制进行 GC

Java: How do you really force a GC using JVMTI's ForceGargabeCollection?

现在...您可能想要这样做,并且您可能想要使用“不保证”系统来提示 GC。 gc() 调用。

从多少字开始有问题?当您需要处理大量单词时,有非常紧凑的数据结构。你确定你使用了正确的数据结构并且你确定你没有泄漏吗?

【讨论】:

  • 有趣的是,一个人因为我问的那个问题而侮辱了我,说我很懒...即使是强硬的那个人已经达到 20K 代表回答更简单的问题,肯定会问那些做得更少的人比我在 JVMTI 上所做的研究。我将他侮辱我的评论标记为冒犯性的,并欢迎任何阅读该问题的人也这样做。此外,如果您认为知道如何真正进行 GC 将是对 GC 的一个很好的补充,那么请支持我的问题。
  • 建立大量代表的最简单方法实际上是在某些领域提出很多人喜欢回答的非常简单的问题。我怀疑我可以通过以随机顺序发布 comp.lang.c 常见问题解答中重新编写的问题来积累数万分的代表。
  • 这里+1。不要理会他们。
【解决方案2】:

您是指未释放给操作系统的内存 - 即top 和类似程序显示 Java 进程占用 1GB 内存?即使 Java 的垃圾收集器从其堆中释放内存,它仍然可以保留内存,以便将来的分配不需要向操作系统请求更多内存。

要查看 Java 对象实际使用了多少堆空间,请使用 VisualVM 或类似的 Java 特定工具。如果您的机器有大量内存,那么 JVM 将使用它(IIRC,尤其是服务器 VM 已调整为保留更多内存),但您始终可以使用 -Xmx 和其他 JVM 选项来限制它。

【讨论】:

  • [Full GC 815615K->815615K(932096K), 1.6976420 secs] [Full GC 815615K->29792K(566272K), 0.2920610 secs] 线程 "main" java.lang.OutOfMemoryError: Java heap java.util.Arrays.copyOfRange(Arrays.java:3209) at java.lang.String.(String.java:216) 的空间是没有明确说明垃圾收集的确切错误。我真的不想增加限制,我想真正解决这个问题。我想问题是如何删除不再需要的数组?
  • 如果你得到这样的错误,那么你可能在某处有参考泄漏。尝试使用内存分析器来确定程序是否保留了对未使用对象的引用,以便 GC 无法释放它们。
【解决方案3】:

好的,你会收到java.lang.OutOfMemoryError: Java heap space
很可能,Runtime.gc() 无济于事,因为如果有,JVM 已经进行了 gc。

这可能是内存泄漏。如果我是你,我会仔细检查我的代码,看看是否有一些引用仍然存在。

所以孙子没有被删除? 当我进行删除时,我只是将其设置为 子节点为空,但不是 孩子的孩子,但那些 孩子们从未被初始化, 只创建(节点 [] 孩子 = 新 节点[26]

如果你这样做 children=null ,是的,整个数组应该是 gc'd。前提是你没有提到某事。

但谁知道罪魁祸首是什么。它甚至可能不是这些“子”节点。您可能想使用 visualVm 并找出正在累积的对象。您可以使用 JProfiler 等更复杂的工具并检查引用等,但如果您只是构建一个 trie,我想遍历代码并发现泄漏会更简单。

【讨论】:

    【解决方案4】:

    分配给进程(即 JVM)的内存不一定会返回给 Unix 中的操作系统。因此,即使 Java 虚拟机可能已经对堆进行了完全垃圾收集,进程大小也可能保持不变。

    通常,这可能不会产生太大影响,因为未使用的堆将被调出并且不会再次调入。查看ps -u 输出中的 Virtual Size (VSZ) 和 Resident Set Size (RSS) 之间的区别,区别在于换出的页面数量。

    【讨论】:

      【解决方案5】:

      只有在任何可访问对象的链接无法再访问该对象时,才会删除该对象。您是否仍有对相关对象的引用?

      顺便说一句,Runtime.gc() 有时只是提示应该运行垃圾收集。

      【讨论】:

      • 所以孙子没有被删除?当我进行删除时,我只是将它的子节点设置为 null,而不是子节点的子节点,但这些子节点从未初始化,只有 create(Node[] children = new Node[26])。