簡體   English   中英

使用Java復制流(模擬telnet)時發生嚴重的內存泄漏

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

下面的鏈接是Telnet的Apache commonsnet示例,它使用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;
}

回應彼得·勞瑞的回答

評論范圍太有限,請允許我在此處輸入。

  • 整個應用程序將一直存在,直到最后一個非守護線程退出為止。

    是的,我應該在不使用主線程join()的情況下設置線程守護程序之一,對嗎?

  • 您不需要設置優先級,它們通常不會按照您的想法做。

  • 中斷要返回/結束的線程沒有意義。

    是的,應該刪除此Interrupt(); 廢話

  • 為什么每當精確讀取13個字節時都要刷新數據?

    好吧,這是我真正的需求所致,這是一個糟糕的設計,但這將是另一個主題,請參閱下文。

  • 您的0長度數據不會在阻塞連接中發生,但如果確實發生,則您的處理方式不正確,如果沒有它,您會更好。

    好吧,實際上此方法是從Apache Commons net復制的,請參見此處: org. Apache. Commons. Net. Io. Util. Copystream(... ) org. Apache. Commons. Net. Io. Util. Copystream(... )

  • CopyStream復制數據直到流結束,但是您將其置於循環中,這意味着10秒鍾后您將截斷文件並用無數據替換它(因為流仍處於關閉狀態,並且您未追加日志文件)

    是的,這就是我所需要的,我只需要最新的命令輸出,上一個命令對我沒有任何意義,如果我不截斷文件,文件很快就會變得越來越大。

  • 您的“讀取器”寫入數據,但不讀取任何內容。 您的“作家”線程可以讀寫。

    是的,也許不是一個好名字,我稍后再更改。

  • 您正在直接使用線程,這被認為是不好的做法。 您應該使用Runnable並用Thread進行包裝,或者使用ExecutorService

    是的,我稍后再更改。

  • 您會捕獲許多異常,但不要將它們打印出來。 勇敢地假設您不需要知道什么時候拋出它們,因為您的線程可能會以靜默方式死掉,您也不知道為什么。

    是的,我稍后會對其進行修改並添加日志。

  • 我假設CopyStreamException是一個Exception,在這種情況下,您麻煩創建一個包裝IOException的不錯的異常,然后將其丟棄。 在啟動閱讀器和編寫器之間,您無需睡覺。

    是的,我想是這樣。

  • 由於您的當前線程僅在等待其他兩個線程,因此可以使用當前線程來編寫“閱讀器”,並且只有一個后台日志記錄線程。

    是的,僅適用於獨立應用程序,實際上這段代碼是從Web應用程序(服務器端)中提取的,主線程有另一項工作,希望我制作的兩個線程永不死(當Web服務器運行時繼續運行)

  • 該方法不會引發FileNotFoundException。

    它拋出,請參見外部方法聲明。

  • 您計算了復制的總數據,但從未使用過。

    是的,應刪除。

  • 要復制流,建議您使用IOUtils。 復制(InputStream,OutputStream)

    我以前使用過,但是它不能滿足我的需要:它將一直阻塞,並且日志文件的大小很快變得太大,以至於我不得不使用自定義的文件。

  • 為什么要讀取13個字節並退出?

    當然,這是一個神奇的數字。 我發現如果讀取了13個字節,那么它將到達輸出的末尾。 顯然這是一個不好的設計,您能幫我找到一種更不錯的方法嗎?

  • 當前一個線程鍵入命令時,服務器將返還大約300Kb的內容。

    我需要一個可以檢測到服務器輸出結束的方法,但是由於while( ( bytes = source. Read( buffer ) )!= -1 )剛剛阻塞,我無法弄清楚它的while( ( bytes = source. Read( buffer ) )!= -1 )

您的程序沒有char[]因此,如果遇到OutOfMemoryError錯誤,則創建char []可以向我們顯示代碼行,該代碼行嗎?

我本來要寫評論,但是太長了。

  • 整個應用程序將一直存在,直到最后一個非守護線程退出為止。
  • 您不需要設置優先級,它們通常不會按照您的想法做。
  • 中斷要返回/結束的線程沒有意義。
  • 當您精確地讀取13個字節時便會刷新,但從不發送確切的13個字節。
  • 您的0長度數據不會在阻塞連接中發生,但如果確實發生,則您的處理方式不正確,如果沒有它,您會更好。
  • copyStream復制數據直到流結束,但是您將其置於循環中,這意味着10秒鍾后,您將截斷文件並將其替換為沒有數據(因為流仍處於關閉狀態,並且您未追加日志文件)
  • 您的“讀取器”寫入數據,但不讀取任何內容。
  • 您的“作家”線程可以讀寫。
  • 您正在直接使用線程,這被認為是不好的做法。 您應該使用Runnable並用Thread進行包裝,或者使用ExecutorService
  • 您會捕獲許多例外,但不要將它們打印出來。 勇敢地假設您不需要知道什么時候拋出它們,因為您的線程可能會以靜默方式死掉,您也不知道為什么。
  • 我假設CopyStreamException是一個Exception,在這種情況下,您麻煩創建一個包裝IOException的不錯的異常,然后將其丟棄。
  • 在啟動閱讀器和編寫器之間,您無需睡覺。
  • 由於您當前的線程僅在等待另外兩個線程,因此您可以使用當前線程來編寫“閱讀器”,並且只有一個后台日志記錄線程。
  • 該方法不會引發FileNotFoundException。
  • 您可以計算出復制的總數據,但是永遠不會使用。
  • 要復制流,建議您使用IOUtils.copy(InputStream,OutputStream)

您是否泄漏來自copyStream方法? 您如何確定的? 要回顯@Peter Lawrey的初始語句,代碼中唯一引用char []的位置是創建的實際字符串。 由於您的程序運行時間很長,因此始終創建要處理的字符串,並假設它們是唯一的字符串(即,JVM的字符串池未重用,而是為每個字符串創建了新的字符串),這可以解釋您的內存使用情況。 您可能只是有很多字符串...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM