Java 删除分配给字节数组的内存
我在套接字服务器的while循环中接收记录。其中每个记录都有一个消息类型,后跟消息长度和实际消息内容 问题是,因为我有大约一百万条记录,而每条记录的大小都是277字节。所以,在大约40000条记录之后,我发现了内存错误。代码流如下所示: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 (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;
}