强制显式删除Java对象

14

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

我在服务器的消息传递组件中添加了System.gc(),希望可以释放内存。此外,我将JVM的堆大小设置为1GB。但我仍然会收到同样数量的异常。

因此我的问题是:如何确保这些数兆字节的消息不会无限期地排队(尽管它们没有被使用)?我能否以某种方式调用"delete"方法来确保它们不占用我的堆空间?

        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

为了回答这里的一些答案,我的代码不存储任何引用。服务器是一个传递对象的Socks代理。我有一个while循环,从传入流中读取对象并将其写入传出流中。就是这样。现在正在研究内存分析器。 - Curious George
2
你读完了《Effective Java》这一章非常快速 ;) - Bozho
你关闭了那些流吗?给一些代码。 - Bozho
不,我们不关闭流。它们在整个程序的生命周期中都是活动的。我会发布一些相关代码。writeToClient是一个ObjectOutputStream对象。readFromServer也是。 - Curious George
我的意思是readFromServer是一个ObjectInputStream对象。 - Curious George
你需要关闭流以允许其内容被垃圾回收。此外,所有现代垃圾收集器都会完全忽略System.gc()调用,它根本不起作用。 - Esko
14个回答

20

对象流会持有每个写入或读取的对象的引用,这是因为序列化协议允许对先前出现在流中的对象进行后向引用。您可能仍然可以使用这种设计,但要使用writeUnshared/ readUnshared代替writeObject/ readObject。我认为,但不确定,这将防止流保持对对象的引用。

正如Cowan所说的,这里还涉及到reset()方法。最安全的做法可能是在向您的ObjectOutputStream写入时立即使用writeUnshared,然后紧接着使用reset()


1
无法点赞足够,因为这几乎肯定是答案。还有一些额外的事情:是的,writeUnshared将完全按照您所说的做。奇怪的是,尽管Javadoc没有保证,但Sun的实现肯定按照您描述的方式工作。其次,另一个选项(特别是如果您想要有限的反向引用,即您编写了相互引用的“集群”对象,但下一个“集群”是单独的)是在“批次”之间使用ObjectOutputStream.reset(),它明确地“忽略已经写入流的任何对象的状态”。 - Cowan
这里再加上一个+1,最终比我的答案高 :) - BalusC

11

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

因此,在问题出现之前自己调用System.gc()并不能解决问题。问题需要在其他地方修复。基本上有两种方式:

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

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

更新: 根据您编辑提供的有关导致此问题的代码的更多信息,请查看此主题中Geoff Reedy的答案,建议使用ObjectInputStream#readUnshared()ObjectOutputStream#writeUnshared()。链接的Javadocs也对此进行了很好的说明。


3
个人而言,我会重新排序它们的位置。 :) - Michael Myers
1
@mmyers,选择2当然更容易,但如果你有漏洞问题,那么你只是稍微延迟了一下问题的出现 ;) - Paolo
@Paolo:我评论后他编辑了,但在5分钟的宽限期结束之前。说真的。 - Michael Myers
的确,在编辑和评论之间的几分钟里,我感到有些不安。 - BalusC
3
有趣的是,不管mmyers做出了什么编辑,他的评论在编辑前后都得到了赞同。 - Bozho

4

System.gc()只是对Java虚拟机的建议。你调用它,JVM可能会或可能不会运行垃圾回收。

OutOfMemoryException可能由两个原因造成。要么你保留了(不需要的)对象引用,要么你接受了太多数据包。

第一种情况可以通过使用分析器来分析,你可以尝试找出还有多少引用仍然存活。服务器内存消耗增加可能是内存泄漏的一个很好的指标。如果每个额外的请求都让你的Java进程增长一点,那么你很可能在某个地方保留了引用(jconsole可能是一个好的开始)。

如果你接受的数据超过了你的处理能力,你将不得不阻止其他请求直到完成之前。


3

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

要了解最佳实践,可以阅读《Effective Java》第二章——关于“创建和销毁对象”。


1
你可以调用显式垃圾回收,但不能保证它会运行。 - Steve B.
1
嘿,我想他的意思是“您可以显式调用GC。” 调用是显式的,但GC不是... ;) - TMN

3

查看您的代码:每次收到或发送数据包时,您的ObjectInput/OutputStream实例是否是新创建的,并且如果是,它们是否被正确关闭?如果没有,您是否在每次读写后调用reset()?对象流类保留对它们所见过的所有对象的引用(以避免在引用相同对象时重复发送),从而防止这些对象被垃圾回收。我大约10年前就遇到了这个问题——实际上那是我第一次使用分析器来诊断内存泄漏……


嗨,如果我可以问一下,这个是否适用于我之前提出的问题:https://dev59.com/mWvXa4cB1Zd3GeqPGB8U - ides
每次我的程序要写Excel文件的时候都会停止...难道是输出流导致了它的停止?谢谢! - ides
@ides:不,不能是同样的问题- 您的代码创建了一个新的输出流并在之后关闭它。 - Michael Borgwardt

2

您无法明确地强制删除,但是您可以通过仅保留一个直接引用并使用Reference对象来持有可垃圾回收的引用,从而确保不仅仅保留对消息的引用。

如果使用一个(小型、有界大小的)队列来处理消息,然后使用次级SoftReference队列来提供给第一个队列,那么您可以保证处理将继续进行,但如果消息太大,则不会出现内存不足错误(在这种情况下,引用队列将被丢弃)。


2
您可以调整Java中的垃圾回收,但不能强制执行。

1
如果你遇到了OutOfMemory异常,那么显然仍有某些东西在持有这些对象的引用。你可以使用像jhat这样的工具来找出这些引用停留的位置。

1

您需要找出是否持有对象的时间比必要的时间长。第一步是在案件上获取分析器,并查看堆,看看为什么对象没有被收集。

尽管您已经给了JVM 1GB,但如果大量对象被快速创建并迫使它们进入较旧的代中,那么您的年轻代可能太小,导致它们不能被快速删除。

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


1
服务器接受来自客户端的数据包(通常很大),并将其转发到其他客户端。
你的代码可能会在转发之前完全接收“数据包”。这意味着它需要足够的内存来完整存储所有数据包,直到它们被完全转发。当这些数据包“很大”时,意味着你确实需要大量的内存。同时也会导致不必要的延迟。
可能你还存在内存泄漏问题,但是如果上述情况属实,那么这种“存储并转发”的设计就是你最大的问题。如果重新设计应用程序,使其不完全接收数据包,而是将其直接流式传输到客户端,即每次仅读取数据包的一小部分并立即将其传输到客户端,那么你可以将内存使用量减少95%。而且你可以通过这种方式来做到与存储和转发相同的外观效果,并不难实现。

通常情况下,我会选择这种设计,但对于我的应用程序来说,丢弃一定百分比的“数据包”非常重要。Socks代理服务器应用程序旨在抽象出恶劣网络环境的环境。我通过此代理运行前向纠错编码方案(每个“数据包”都是LT代码块)。 - Curious George
@Curious George:我不明白为什么你必须要丢弃数据包,除非你在编写测试,但即使是这样,当一个特定的数据包到达时,你也可以决定是否丢弃它,并在这种情况下向源客户端返回错误,或者简单地耗尽输入流而不存储数据。 - Michael Borgwardt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接