使用Java复制流(模拟telnet)时出现严重的内存泄漏
下面的链接是 Apache commonsnet 的 Telnet 示例,它模拟用户使用 telnet 客户端(键入 telnet 命令并获取输出),一个线程正在读取流,一个线程正在写入流。
http://www.docjar.com/html/api /examples/weatherTelnet.java.html
我修改了这个程序,用户可以登录telnet服务器并键入命令,但永远不会注销。大约每隔 15 秒,用户键入一个命令,服务器就会向他提供输出。我使用另一个线程将服务器输出复制到本地文件,但程序可以运行,但会导致严重的内存泄漏,大约几个小时后程序因OutofMemoryException而退出,使用Java堆内存转储工具我可以看到根原因是char[],这肯定是在复制流期间引起的。
有人可以帮我指出问题出在哪里以及如何解决吗?非常感谢。
public static final void readWrite( final InputStream remoteInput, final OutputStream remoteOutput,
final String address ) throws FileNotFoundException, InterruptedException
{
Thread reader = null;
Thread writer = null;
final String[] commands = new String[]{ "username\n", "password\n", "command\n" };
reader = new Thread("reader")
{
@SuppressWarnings( "null" )
@Override
public void run()
{
String currentCommand = null;
try
{
int i = 0;
while( !interrupted() )
{
if( i >= 3 )
currentCommand = "command\n";
else
currentCommand = commands[i++];
remoteOutput.write( currentCommand.getBytes() );
remoteOutput.flush();
sleep( 15000 );
}
}
catch( IOException e )
{
e.printStackTrace();
this.interrupt();
}
catch( InterruptedException e )
{
this.interrupt();
}
}
};
writer = new Thread("writer" )
{
@Override
public void run()
{
try
{
while( !isInterrupted() )
{
FileOutputStream fos = new FileOutputStream( "logfile" );
copyStream( remoteInput, fos, 1024, false );
fos.close();
sleep( 10000 );
}
}
catch( IOException e )
{
this.interrupt();
}
catch( InterruptedException e )
{
this.interrupt();
}
}
};
writer.setPriority( Thread.currentThread().getPriority() + 1 );
}
reader.start();
Thread.sleep( 2000 );
writer.start();
Thread.currentThread().join();
}
public static final long copyStream( InputStream source, OutputStream dest, int bufferSize, boolean flush )
throws CopyStreamException
{
int bytes;
long total;
byte[] buffer;
buffer = new byte[ bufferSize ];
total = 0;
try
{
while( ( bytes = source.read( buffer ) ) != -1 )
{
// Technically, some read(byte[]) methods may return 0 and we cannot
// accept that as an indication of EOF.
if( bytes == 13 )
{
dest.flush();
break;
}
if( bytes == 0 )
{
bytes = source.read();
if( bytes < 0 )
break;
dest.write( bytes );
if( flush )
dest.flush();
++total;
continue;
}
dest.write( buffer, 0, bytes );
if( flush )
dest.flush();
total += bytes;
}
}
catch( IOException e )
{
throw new CopyStreamException( "IOException caught while copying.", total, e );
}
return total;
}
回复 Peter Lawrey 的回答
评论区太有限,请让我在这里输入。
整个应用程序将一直存在,直到最后一个非守护线程退出。
<块引用>是的,我应该设置一个线程守护程序而不使用主线程 join(),对吗?
你不需要设置优先级,它们通常不会按照你的想法做。
<块引用>好的
没有必要中断即将返回/完成的线程。
<块引用>是的,应该删除这个
Interrupt();
这是无意义的。为什么每次读取 13 个字节时都要刷新数据?
<块引用>嗯,由于我的真实需求,这是一个糟糕的设计,但这将是另一个主题,请稍后参阅。
对于阻塞连接,您的 0 长度数据不会发生,但如果发生了,您的处理方式不正确,最好不要这样做。
<块引用>嗯,实际上这个方法是从 Apache Commons net 复制的,请参见这里:
org.阿帕奇。公共资源。网。艾欧。实用程序。 Copystream(...)
CopyStream 复制数据直到流结束,但是您将其置于循环中,这意味着 10 秒后您将截断文件并将其替换为无数据 (因为流仍然关闭并且您不附加日志文件)
<块引用>是的,这就是我需要的,我只需要最新的命令输出,之前的命令输出对我来说没有任何意义,如果我不截断文件,很快文件就会变得越来越大。
CopyStream您的“阅读器”写入数据,但不读取任何内容。您的“编写器”线程既可以读取也可以写入。
<块引用>是的,也许不是一个好名字,我稍后会更改它。
您直接使用Thread,这被认为是不好的做法。您应该使用 Runnable 并用 Thread 包装它或使用 ExecutorService
<块引用>是的,我稍后会更改。
您捕获了许多异常,但没有将它们打印出来。假设您不需要知道它们何时被抛出,这是勇敢的,因为您的线程可能会默默地死亡,而您不会知道为什么。
<块引用>是的,我稍后会修改它并在其中记录日志。
我假设 CopyStreamException 是一个异常,在这种情况下,您已经费尽心思创建一个很好的异常来包装 IOException,你稍后会扔掉它。 您不需要在启动 reader 和 writer 之间睡觉。
<块引用>是的,我想是的。
由于您当前的线程只是在等待其他两个线程,因此您可以使用当前线程来执行写入“读取器”操作,并且只有一个后台日志记录线程。
<块引用>是的,仅适用于独立应用程序,实际上这段代码是从Web应用程序(服务器端)中提取的,主线程有另一个工作,我希望我创建的两个线程永远不会死(当Web 服务器正在运行)
由于您当前的线程只是在等待其他两个线程,因此该方法不会抛出 FileNotFoundException。
<块引用>它会抛出异常,请参阅外部方法声明。
您计算了复制的总数据,但从未使用过它。
<块引用>是的应该删除。
要复制流,我建议您使用 IOUtils。复制(输入流,输出流)
<块引用>我以前用过,但它不能满足我的需求:它会一直阻塞,而且日志文件的大小很快就会变得太大,我必须使用自定义的。
为什么要读13字节并退出?
<块引用>这确实是一个神奇的数字。我发现如果读取 13 个字节,那么它就会到达输出的末尾。显然这是一个糟糕的设计,你能帮我找到一个更好的方法吗?
当前一个线程输入命令时,服务器会返回大约 300Kb 的内容。
<块引用>我需要一种可以检测服务器输出结束的方法,但我无法弄清楚,因为
while( ( bytes = source.Read( buffer ) )!= -1 )刚刚被阻止!
below link is a Apache commonsnet example of Telnet, it simulate the user using telnet client(type the telnet command and get output), one thread is reading stream and one thread is writing stream.
http://www.docjar.com/html/api/examples/weatherTelnet.java.html
i modify this program, the user can login in the telnet server and type command but never logged out. about every 15 second, the user type a command and the server give him output. i use another thread to copy the server output to local file, but the program can work, but it will cause serious memory leak, about a few hours later the program quit because of OutofMemoryException, using Java heap memory dump tool i can see the root cause is char[] which is definitely caused during copy stream.
Could somebody help me point out where the problem is and how to fix? thanks very much.
public static final void readWrite( final InputStream remoteInput, final OutputStream remoteOutput,
final String address ) throws FileNotFoundException, InterruptedException
{
Thread reader = null;
Thread writer = null;
final String[] commands = new String[]{ "username\n", "password\n", "command\n" };
reader = new Thread("reader")
{
@SuppressWarnings( "null" )
@Override
public void run()
{
String currentCommand = null;
try
{
int i = 0;
while( !interrupted() )
{
if( i >= 3 )
currentCommand = "command\n";
else
currentCommand = commands[i++];
remoteOutput.write( currentCommand.getBytes() );
remoteOutput.flush();
sleep( 15000 );
}
}
catch( IOException e )
{
e.printStackTrace();
this.interrupt();
}
catch( InterruptedException e )
{
this.interrupt();
}
}
};
writer = new Thread("writer" )
{
@Override
public void run()
{
try
{
while( !isInterrupted() )
{
FileOutputStream fos = new FileOutputStream( "logfile" );
copyStream( remoteInput, fos, 1024, false );
fos.close();
sleep( 10000 );
}
}
catch( IOException e )
{
this.interrupt();
}
catch( InterruptedException e )
{
this.interrupt();
}
}
};
writer.setPriority( Thread.currentThread().getPriority() + 1 );
}
reader.start();
Thread.sleep( 2000 );
writer.start();
Thread.currentThread().join();
}
public static final long copyStream( InputStream source, OutputStream dest, int bufferSize, boolean flush )
throws CopyStreamException
{
int bytes;
long total;
byte[] buffer;
buffer = new byte[ bufferSize ];
total = 0;
try
{
while( ( bytes = source.read( buffer ) ) != -1 )
{
// Technically, some read(byte[]) methods may return 0 and we cannot
// accept that as an indication of EOF.
if( bytes == 13 )
{
dest.flush();
break;
}
if( bytes == 0 )
{
bytes = source.read();
if( bytes < 0 )
break;
dest.write( bytes );
if( flush )
dest.flush();
++total;
continue;
}
dest.write( buffer, 0, bytes );
if( flush )
dest.flush();
total += bytes;
}
}
catch( IOException e )
{
throw new CopyStreamException( "IOException caught while copying.", total, e );
}
return total;
}
Responding to Peter Lawrey's Answer
The comment area is too limited please let me type it here.
The whole application will exist until the last non-daemon thread exits.
yes, I should set one of the thread daemon without using the main thread join(), right?
You don't need to set priorities, they usually don't do what you think.
OK
There is no point interrupting a thread which is about to return/finish.
Yes, should remove this
Interrupt();
it's nonsense.Why would you flush the data whenever you read exactly 13 bytes?
Well it's a bad design beause of my real demand, but that would be another topic, see later about this.
Your 0 length data won't happen for a blocking connection, but if it did, your handling isn't correct and you better off without it.
Well, actually this method is copied from Apache Commons net, see here:
org. Apache. Commons. Net. Io. Util. Copystream(... )
CopyStream copies the data until the stream ends, however you have this in a loop which means 10 seconds later you will truncate the file and replace it with no data (as the stream is still closed and you don't append the log file)
Yes that's what I need, I only need the latest command output, previous one has no meaning to me, if I didn't truncate the file, soon the file will become bigger and bigger.
Your "reader" writes data, it doesn't read anything. Your "writer" thread both reads and writes.
Yes maybe not a good name, I'll change it later.
You are using Thread directly which is considered bad practice. You should use a Runnable and wrap it with a Thread or use an ExecutorService
Yes, I'll change it later.
You catch a number of exceptions but don't print them out. It is brave to assume you don't need to know when they are thrown as your thread might die silently and you won't know why.
Yes, I'll modify it later and have logs in it.
I assume CopyStreamException is an Exception, in which case you have gone to the trouble of creating a nice exception to wrap the IOException, which you later throw away.
You don't need to sleep between starting the reader and the writer.Yes I think so.
As your current thread is just waiting for two other threads you could use the current thread to do the writing "reader" and have just one background logging thread.
Yes only for standalone application, in fact this piece of code is extracted from a web application(server side), the main thread has another job, I hope the two thread I made never die(keep running when the web server running)
The method doesn't throw FileNotFoundException.
It throws, see the outer method declaration.
You calculate the total data copied, but it is never used.
Yes should be removed.
To copy a stream I suggest you use IOUtils. Copy(InputStream, OutputStream)
I used before, however it did not meet my need: it will block all the time, and the log file's size soon get too huge that I have to use a customized one.
Why read 13 byte and quit?
Surely it's a magic number. I found if 13 bytes is read then it reach to the output's end. Obvious it's a bad design, could you help me found a more NICE way?
When the former thread type a command, the server will give back about 300Kb content.
I need a method which can detect the end of server output, but I fail to figure it out because the
while( ( bytes = source. Read( buffer ) )!= -1 )
just blocked!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您的程序没有
char[]
因此,如果您在创建 char[] 时遇到 OutOfMemoryError 错误,可以向我们显示执行此操作的代码行吗?本来想写评论的,但是太长了。
Your program doesn't have a
char[]
so if you are getting an OutOfMemoryError creating a char[] can to show us the line of code which does this?I was going to write a comment, but it was far too long.
您认为泄漏是来自 copyStream 方法吗?你是如何确定的?为了回显@Peter Lawrey的初始声明,代码中唯一引用 char[] 的地方是创建的实际字符串。由于您的程序运行很长一段时间,总是创建要处理的字符串,并假设这些是唯一的字符串(即,不被 JVM 的字符串池重用,而是为每个字符串创建一个新字符串),这可以解释您的内存使用情况。你可能只是有很多字符串......
Are you that the leak is from the copyStream method? How did you determine that? To echo @Peter Lawrey initial statement, the only places in your code that refer to char[] are the actual Strings created. Since your program runs for a long time, always creating Strings to be processed, and assuming these are unique Strings (ie, not reused by the JVM's String pool, but a new String created for each), that could explain your memory usage. You may simply have a lot of Strings...