Java 删除分配给字节数组的内存

Java 删除分配给字节数组的内存,java,sockets,out-of-memory,heap-memory,Java,Sockets,Out Of Memory,Heap Memory,我在套接字服务器的while循环中接收记录。其中每个记录都有一个消息类型,后跟消息长度和实际消息内容 问题是,因为我有大约一百万条记录,而每条记录的大小都是277字节。所以,在大约40000条记录之后,我发现了内存错误。代码流如下所示: while (true) { msgType = dIn.readByte(); int msgIntType = msgType & 0xff;

我在套接字服务器的while循环中接收记录。其中每个记录都有一个消息类型,后跟消息长度和实际消息内容

问题是,因为我有大约一百万条记录,而每条记录的大小都是277字节。所以,在大约40000条记录之后,我发现了内存错误。代码流如下所示:

while (true) {              
            msgType = dIn.readByte();

            int msgIntType = msgType & 0xff;

                  // get message length

                  int msgIntLen = dIn.readInt();
                  if (msgIntLen != 0) {

     msgContent = new byte[msgIntLen];
                   switch(msgIntType) {
            case 4:
            //case MSG_DATA:
                // MSG_DATA
                recordCount++;
                processData(msgContent);
                if (recordCount == 2000) {
                sendACK(dOut, msgIntType);
                logger.info("sent ACK for MSG_DATA");
                recordCount = 0;
                }               
                break;

}
我解决了OutOfMemory问题,在每处理2000条记录后发送ACK后显式调用System.gc(),现在它工作得非常好,能够在不到10分钟的时间内处理100万条记录而没有任何错误。调用System.gc()的case语句的修改代码如下所示:

            case 4:
            //case MSG_DATA:
                // MSG_DATA
                recordCount++;
                processData(msgContent);
                if (recordCount == 2000) {
                sendACK(dOut, msgIntType);
                logger.info("sent ACK for MSG_DATA");
                recordCount = 0;
                             System.gc();
                }               
                break;
但我在其他一些帖子上读到,调用System.gc()不是一种好的设计方法?是这样吗?如果是的话,你们能给我一些其他的方法来消除这个OutOfMemory错误吗

提前谢谢 -JJ

编辑:processData()的逻辑:


您是否未能关闭某些资源并依赖Finalizer线程来获取它们?或者您刚刚添加了一个最终确定器(可能是不必要的),它会阻止大量内存被迅速释放。

如果这确实是您所做的唯一更改,那么很难看出这将如何解决问题。每当Java虚拟机内存不足时,它都会在抛出内存不足异常之前自动运行垃圾收集器。这样做既没有必要,也没有任何价值

对于您描述的问题,唯一真正的解决方案是确保清除对不再需要的对象的任何引用。就像你说的:

byte[] ba=new byte[bignumber];
process(ba);

然后你继续做其他的事情,ba仍然坐在那里,吞噬着记忆。您想要退出定义它的函数,或者设置ba=null以丢失引用。然后gc可以回收内存。

您接收的数据是否有最大大小(或者可以强制执行)?在这种情况下,您可以在每次迭代时声明您的字节数组outisde并重用它,而无需分配更多内存:

...
private static final int BUFFER_SIZE = 102400; //start with a buffer big enough to lower the chances of resizing it -- e.g. 100K
...
msgContent = new byte[BUFFER_SIZE];
while (true) {              
            msgType = dIn.readByte();

            int msgIntType = msgType & 0xff;

                  // get message length

                  int msgIntLen = dIn.readInt();
                  if (msgIntLen != 0) {
                   if( msgIntLen > msgContent.length ) //only resize when needed otherwise reuse
                     msgContent = new byte[msgIntLen];

                   switch(msgIntType) {
            case 4:
            //case MSG_DATA:
                // MSG_DATA
                recordCount++;
                processData(msgContent, msgIntLen); //need to change your function to also pass in the size of the message read!
                if (recordCount == 2000) {
                sendACK(dOut, msgIntType);
                logger.info("sent ACK for MSG_DATA");
                recordCount = 0;
                }               
                break;

}

dIN
变量的类型是什么?也许我误解了,但是你真的需要把你的输入读入一个字节数组,然后把字节数组看作一个流,然后逐行读取流吗? 如果您已经知道内容的结构,为什么要创建所有中间步骤。您也可以通过某种方式
处理(dIn)


另外,我想确认一下,这是在多线程环境中运行的吗?

processData()的作用是什么?它是否可能以某种方式保留对字节数组的引用?我建议您对内存不足错误进行堆转储并对其进行分析。它应该显示你的记忆是否被保留。它可能指向一个解决方案。@Peter-你能告诉我怎么做吗?这可能真的很有帮助。我们在程序失败后进行转储?(我猜:)添加选项
-XX:+HeapDumpOnOutOfMemoryError
,然后加载像YourKit这样的工具中生成的转储。我认为VisualVM也可以加载它。这与问题无关,但是没有必要在那里设置
最终
块(甚至
关闭
,因为没有实际的资源参与。(我认为将
关闭
放在
输入流
读取器
上可能是一个错误)我在case语句中尝试了ba=bull-before-break,但没有任何帮助为我解决了这个问题。你绝对确定你只是添加了那一行,然后程序就运行了吗?因为根据文档,这是不可能的。坦率地说,这听起来像是说你键入了x=2+2,而x最终是5。我想这并不是不可能的,可能是Java中有一个bug或者我的一些微妙之处但是我看到JVM的描述一再强调,当内存不足时gc会自动运行,并调用gc()从来没有必要。我会照现在的样子处理您的程序,删除该gc调用,不做任何更改,然后看看它是否失败。我也尝试过这个解决方案,但我们正在为不同大小的数据流运行这个JVM,而之前我们不知道buffersize。您可以根据需要调整它以扩展缓冲区,这样您的缓冲区只会在buffer大小需要扩展,否则只需重新使用已分配的缓冲区。我将编辑我的答案,向您展示如何。正确的是,它没有在多线程环境中运行。我正在读取字节数组中的输入,因为我将msg接收为字节数组,其中第一个字节是消息类型,后面是4个字节的msg长度,后面是msg内容字节.I读取msg type和msg length,然后传递字节数组内容,该内容是ascii字符串。现在在process()中,我rcv该字节数组,并在该func的代码中执行上述步骤wrttn,并将其添加到stringbuilder对象中。当这些记录(即多个process()调用)时达到我们想要的缓冲区大小,然后使用stringbuilder输出将此缓冲区转储到文件中。例如,在显示
processData()
功能的更新中,您应该真正关闭InputStreamReader
...
private static final int BUFFER_SIZE = 102400; //start with a buffer big enough to lower the chances of resizing it -- e.g. 100K
...
msgContent = new byte[BUFFER_SIZE];
while (true) {              
            msgType = dIn.readByte();

            int msgIntType = msgType & 0xff;

                  // get message length

                  int msgIntLen = dIn.readInt();
                  if (msgIntLen != 0) {
                   if( msgIntLen > msgContent.length ) //only resize when needed otherwise reuse
                     msgContent = new byte[msgIntLen];

                   switch(msgIntType) {
            case 4:
            //case MSG_DATA:
                // MSG_DATA
                recordCount++;
                processData(msgContent, msgIntLen); //need to change your function to also pass in the size of the message read!
                if (recordCount == 2000) {
                sendACK(dOut, msgIntType);
                logger.info("sent ACK for MSG_DATA");
                recordCount = 0;
                }               
                break;

}