简体   繁体   English

使用Java复制流(模拟telnet)时发生严重的内存泄漏

[英]Serious memory leak when copying stream(simulate telnet) using Java

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. 下面的链接是Telnet的Apache commonsnet示例,它使用telnet客户端(键入telnet命令并获取输出)模拟用户,一个线程正在读取流,一个线程正在写入流。

http://www.docjar.com/html/api/examples/weatherTelnet.java.html 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. 我修改了该程序,用户可以登录telnet服务器并键入命令,但从未注销。 about every 15 second, the user type a command and the server give him output. 大约每15秒钟,用户键入一个命令,服务器将其输出。 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. 我使用另一个线程将服务器输出复制到本地文件,但是该程序可以运行,但是会导致严重的内存泄漏,大约几个小时后,由于OutofMemoryException,程序退出了,使用Java堆内存转储工具,我可以看到根目录原因是char [] ,它肯定是在复制流期间引起的。

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? 是的,我应该在不使用主线程join()的情况下设置线程守护程序之一,对吗?

  • 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(); 是的,应该删除此Interrupt(); it's nonsense. 废话

  • Why would you flush the data whenever you read exactly 13 bytes? 为什么每当精确读取13个字节时都要刷新数据?

    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. 您的0长度数据不会在阻塞连接中发生,但如果确实发生,则您的处理方式不正确,如果没有它,您会更好。

    Well, actually this method is copied from Apache Commons net, see here: org. Apache. Commons. Net. Io. Util. Copystream(... ) 好吧,实际上此方法是从Apache Commons net复制的,请参见此处: org. Apache. Commons. Net. Io. Util. Copystream(... ) 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) CopyStream复制数据直到流结束,但是您将其置于循环中,这意味着10秒钟后您将截断文件并用无数据替换它(因为流仍处于关闭状态,并且您未追加日志文件)

    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 您应该使用Runnable并用Thread进行包装,或者使用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. 我假设CopyStreamException是一个Exception,在这种情况下,您麻烦创建一个包装IOException的不错的异常,然后将其丢弃。 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) 是的,仅适用于独立应用程序,实际上这段代码是从Web应用程序(服务器端)中提取的,主线程有另一项工作,希望我制作的两个线程永不死(当Web服务器运行时继续运行)

  • The method doesn't throw FileNotFoundException. 该方法不会引发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. 要复制流,建议您使用IOUtils。 Copy(InputStream, OutputStream) 复制(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? 为什么要读取13个字节并退出?

    Surely it's a magic number. 当然,这是一个神奇的数字。 I found if 13 bytes is read then it reach to the output's end. 我发现如果读取了13个字节,那么它将到达输出的末尾。 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. 当前一个线程键入命令时,服务器将返还大约300Kb的内容。

    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! 我需要一个可以检测到服务器输出结束的方法,但是由于while( ( bytes = source. Read( buffer ) )!= -1 )刚刚阻塞,我无法弄清楚它的while( ( bytes = source. Read( buffer ) )!= -1 )

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? 您的程序没有char[]因此,如果遇到OutOfMemoryError错误,则创建char []可以向我们显示代码行,该代码行吗?

I was going to write a comment, but it was far too long. 我本来要写评论,但是太长了。

  • The whole application will exist until the last non-daemon thread exits. 整个应用程序将一直存在,直到最后一个非守护线程退出为止。
  • You don't need to set priorities, they usually don't do what you think. 您不需要设置优先级,它们通常不会按照您的想法做。
  • There is no point interrupting a thread which is about to return/finish. 中断要返回/结束的线程没有意义。
  • You flush when you read exactly 13 bytes, but you never send exact 13 bytes. 当您精确地读取13个字节时便会刷新,但从不发送确切的13个字节。
  • 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. 您的0长度数据不会在阻塞连接中发生,但如果确实发生,则您的处理方式不正确,如果没有它,您会更好。
  • 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) copyStream复制数据直到流结束,但是您将其置于循环中,这意味着10秒钟后,您将截断文件并将其替换为没有数据(因为流仍处于关闭状态,并且您未追加日志文件)
  • your "reader" writes data, it doesn't read anything. 您的“读取器”写入数据,但不读取任何内容。
  • your "writer" thread both reads and writes. 您的“作家”线程可以读写。
  • 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 您应该使用Runnable并用Thread进行包装,或者使用ExecutorService
  • 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. 勇敢地假设您不需要知道什么时候抛出它们,因为您的线程可能会以静默方式死掉,您也不知道为什么。
  • 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. 我假设CopyStreamException是一个Exception,在这种情况下,您麻烦创建一个包装IOException的不错的异常,然后将其丢弃。
  • you don't need to sleep between starting the reader and the writer. 在启动阅读器和编写器之间,您无需睡觉。
  • 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. 由于您当前的线程仅在等待另外两个线程,因此您可以使用当前线程来编写“阅读器”,并且只有一个后台日志记录线程。
  • the method doesn't throw FileNotFoundException. 该方法不会引发FileNotFoundException。
  • you calculate the total data copied, but it is never used. 您可以计算出复制的总数据,但是永远不会使用。
  • To copy a stream I suggest you use IOUtils.copy(InputStream, OutputStream) 要复制流,建议您使用IOUtils.copy(InputStream,OutputStream)

Are you that the leak is from the copyStream method? 您是否泄漏来自copyStream方法? 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. 要回显@Peter Lawrey的初始语句,代码中唯一引用char []的位置是创建的实际字符串。 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. 由于您的程序运行时间很长,因此始终创建要处理的字符串,并假设它们是唯一的字符串(即,JVM的字符串池未重用,而是为每个字符串创建了新的字符串),这可以解释您的内存使用情况。 You may simply have a lot of Strings... 您可能只是有很多字符串...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM