14

我正在处理一个处理大量非常密集的流量的 Java 服务器。服务器接受来自客户端的数据包(通常为数兆字节)并将它们转发给其他客户端。服务器从不明确存储任何传入/传出的数据包。然而,服务器不断地遇到OutOfMemoryException异常。

我添加System.gc()到服务器的消息传递组件中,希望可以释放内存。此外,我将 JVM 的堆大小设置为千兆字节。我仍然遇到同样多的例外。

所以我的问题是:如何确保兆字节消息不会无限期地排队(尽管不需要)?有没有办法让我在这些对象上调用“删除”以保证它们没有使用我的堆空间?

        try
        {
           while (true)
            {
               int r = generator.nextInt(100);//generate a random number between 0 and 100
                Object o =readFromServer.readObject();
                sum++;
                // if the random number is larger than the drop rate, send the object to client, else
                //it will be dropped
                if (r > dropRate)
                {
                    writeToClient.writeObject(o);
                    writeToClient.flush();
                    numOfSend++;
                    System.out.printf("No. %d send\n",sum);
                }//if

            }//while
        }//try
4

14 回答 14

19

对象流包含对从它们写入/读取的每个对象的引用。这是因为序列化协议允许反向引用流中较早出现的对象。您可能仍然可以使用此设计,但使用 writeUnshared/readUnshared 而不是 writeObject/readObject。我认为,但不确定,这将阻止流保持对对象的引用。

正如考恩所说,这种reset()方法也在这里发挥作用。最安全的做法可能是在写入您的 s时writeUnshared立即使用reset()ObjectOutputStream

于 2010-02-01T16:32:13.187 回答
11

当 JVM 处于 边缘时OutOfMemoryError,它将运行GC。

所以System.gc()事先打电话给自己并不能解决问题。问题是在其他地方解决。基本上有两种方式:

  1. 编写内存高效代码和/或修复代码中的内存泄漏。
  2. 给 JVM 更多内存。

使用Java Profiler可能会提供大量有关内存使用和潜在内存泄漏的信息。

更新:根据您的编辑以及有关导致此问题的代码的更多信息,请查看Geoff Reedy 在本主题中的回答,该回答建议使用ObjectInputStream#readUnshared()andObjectOutputStream#writeUnshared()代替。(链接的)Javadocs 也很好地解释了它。

于 2010-02-01T16:05:09.883 回答
4

System.gc() 只是对 Java 虚拟机的推荐。您调用它,JVM 可能会也可能不会运行垃圾收集。

OutOfMemoryException 可能是由两件事引起的。要么保留(不需要的)对对象的引用,要么接受许多数据包。

第一种情况可以通过分析器进行分析,您可以在其中尝试找出有多少引用仍然存在。内存韭菜的一个很好的迹象是服务器的内存消耗正在增加。如果每个额外的请求都会使您的 Java 进程增长一点,那么您很可能会在某处保留引用(jconsole 可能是一个好的开始)

如果您接受的数据超出您的处理能力,您将不得不阻止其他请求,直到其他请求完成。

于 2010-02-01T16:09:14.260 回答
3

您不能调用显式垃圾收集。但这不是这里的问题。也许您正在存储对这些消息的引用。跟踪它们的处理位置并确保在使用它们后没有对象持有对它们的引用。

要更好地了解最佳实践是什么,请阅读Effective Java,第 2 章- 它是关于“创建和销毁对象”的

于 2010-02-01T16:04:52.690 回答
3

查看您的代码:ObjectInput/OutputStream每次数据包到达或发送时,您的实例是否都是新创建的,如果是,它们是否正确关闭?如果没有,您是否reset()在每次读/写后调用?对象流类保留对他们看到的所有对象的引用(为了避免每次引用相同的对象时重新发送),防止它们被垃圾收集。大约 10 年前我遇到了这个确切的问题 - 实际上我第一次不得不使用分析器来诊断内存泄漏......

于 2010-02-01T17:03:40.337 回答
2

您不能显式强制删除,但您可以确保不保留对消息的引用,方法是仅在内存中保留一个直接引用,然后使用 Reference 对象保存对它的垃圾回收引用。

使用(小,有界)队列来处理消息,然后使用辅助 SoftReference 队列馈送到第一个队列呢?这样,您可以保证处理将继续进行,但如果消息太大,您也不会出现内存错误(在这种情况下,引用队列将被转储)。

于 2010-02-01T16:05:24.483 回答
2

您可以在 java 中调整垃圾收集,但不能强制。

于 2010-02-01T16:06:07.247 回答
1

如果您遇到 OutOfMemory 异常,则显然某些东西仍然持有对这些对象的引用。您可以使用诸如jhat 之类的工具来找出这些引用的位置。

于 2010-02-01T16:05:53.823 回答
1

您需要确定是否持有物体的时间超过了必要的时间。第一步是获取有关案例的分析器并查看堆,看看为什么没有收集对象。

尽管您已经为 JVM 分配了 1GB 空间,但如果大量对象被快速创建并迫使它们进入老一代而不会很快被删除,那么您的年轻代可能太小了。

有关 GC 调整的一些有用信息:http: //java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html

于 2010-02-01T16:06:34.673 回答
1

服务器接受来自客户端的数据包(通常为数兆字节)并将它们转发给其他客户端。

您的代码可能在转发它们之前完全接收到“数据包”。这意味着它需要足够的内存来完全存储所有数据包,直到它们被完全转发,并且当这些数据包“数兆字节大”时,这意味着您确实需要大量内存。它还会导致不必要的延迟。

您也可能有内存泄漏,但如果上述情况属实,那么这种“存储和转发”设计就是您最大的问题。如果您将应用程序重新设计为不完全接收数据包,而是将它们直接流式传输到客户端,即一次只读取包的一小部分并立即将其传输到客户端,您可能可以将内存使用量减少 95%。以一种在客户看来与进行存储和转发时完全相同的方式来执行此操作并不难。

于 2010-02-01T16:19:43.467 回答
0

正如其他人在此处发布的那样,手动触发 System.gc 不是一个好的答案。它不能保证运行,并且会触发完整的 gc,这可能会在运行时将您的服务器挂起很长时间(如果您为服务器提供 GB 的内存,则 > 1 秒,我已经看到了几分钟大型系统上的长时间停顿)。您可以调整您的 gc,这肯定会有所帮助,但不能完全解决问题。

如果您正在从一个流中读取对象,然后将它们写出到另一个流中,那么您就需要将整个对象保存在内存中。如您所说,如果这些对象很大,那么这可能是您的问题。尝试重写您的 IO,以便您从 1 个流中读取字节并将它们写入另一个流,而无需显式保存完整的对象(尽管如果您需要验证/验证对象,我看不出这将如何与对象序列化/反序列化一起工作)。

于 2010-02-01T16:25:04.720 回答
0

只是添加到所有以前的回复中: System.gc() 不是 JVM 启动垃圾收集的命令。它是一个温和的方向,不保证会发生任何事情。JVM 规范将其留给供应商来决定在 gc 调用上需要做什么。供应商甚至可能选择什么都不做!

于 2010-02-01T16:26:04.500 回答
0

您提到您在发送之前明确需要整个收到的数据包?好吧,这并不意味着您需要将其全部存储在内存中,是吗?将接收到的数据包保存到外部存储(如果 SSD 太慢,可能是 ram-disk 或 DB)然后将它们直接通过管道传输到接收者而不将它们完全加载到内存中,这是一种可行的架构更改吗?

于 2010-02-01T16:33:50.597 回答
0

如果您的服务器在死机前至少运行了几分钟,您可能想尝试在 Visual VM 中运行它。您至少可以更好地了解堆的增长速度以及其中的对象类型。

于 2010-02-01T17:02:40.637 回答