Серьезная утечка памяти при копировании потока (симуляция telnet) с использованием Java - PullRequest
0 голосов
/ 21 января 2011

ниже ссылка является примером 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 (), верно?

  • Вам не нужно устанавливать приоритеты, они обычно не делают то, что вы думаете.

    OK

  • Нет смысла прерывать поток, который собирается вернуться / завершить.

    Да, следует удалить это Interrupt(); это нонсенс.

  • Зачем вам сбрасывать данные всякий раз, когда вы читаете ровно 13 байтов?

    Ну, это плохой дизайн из-за моего реального спроса, но это будет другой темой, см. Позже об этом.

  • Ваши данные длины 0 не будут получены для блокирующего соединения, но если это произойдет, ваша обработка будет неправильной, и вам будет лучше без нее.

    Ну, на самом деле этот метод скопирован из сети Apache Commons, см. Здесь: org. Apache. Commons. Net. Io. Util. Copystream(... )

  • CopyStream копирует данные до тех пор, пока поток не закончится, однако у вас есть это в цикле, что означает, что через 10 секунд вы урежете файл и замените его без данных (так как поток все еще закрыт, и вы не добавить файл журнала)

    Да, это то, что мне нужно, мне нужен только последний вывод команды, предыдущий не имеет для меня значения, если я не усекаю файл, скоро файл станет больше и больше.

  • Ваш «читатель» записывает данные, он ничего не читает. Ваша ветка "писатель" читает и пишет.

    Да, может быть, не очень хорошее имя, я поменяю его позже.

  • Вы используете Thread напрямую, что считается плохой практикой. Вы должны использовать Runnable и обернуть его потоком или использовать ExecutorService

    Да, я изменю это позже.

  • Вы ловите несколько исключений, но не распечатываете их. Смело полагать, что вам не нужно знать, когда они брошены, поскольку ваша нить может умереть молча, и вы не будете знать, почему.

    Да, я изменю его позже и у меня будут логи.

  • Я предполагаю, что CopyStreamException является Исключением, и в этом случае вы столкнулись с проблемой создания хорошего исключения, чтобы обернуть IOException, которое вы позже выбросите. Вам не нужно спать между началом чтения и записи.

    Да, я так думаю.

  • Поскольку ваш текущий поток просто ожидает двух других потоков, вы можете использовать текущий поток, чтобы написать «читатель», и иметь только один поток фоновой регистрации.

    Да, только для автономного приложения, фактически этот фрагмент кода извлекается из веб-приложения (на стороне сервера), основной поток выполняет другую работу, я надеюсь, что два созданных мной потока никогда не умрут (продолжайте работу, когда веб-сервер работает)

  • Метод не вызывает исключение FileNotFoundException.

    Выдает, см. Объявление внешнего метода.

  • Вы рассчитываете общее количество скопированных данных, но они никогда не используются.

    Да, следует удалить.

  • Для копирования потока я предлагаю вам использовать IOUtils. Копировать (InputStream, OutputStream)

    Я использовал раньше, однако это не отвечало моим потребностям: оно будет постоянно блокироваться, и вскоре размер файла журнала станет слишком большим, поэтому мне придется использовать пользовательский.

  • Зачем читать 13 байт и выходить?

    Конечно, это магическое число. Я обнаружил, что если 13 байтов прочитано, то оно достигает конца вывода. Очевидно, что это плохой дизайн, не могли бы вы помочь мне найти более хороший способ?

  • Когда прежний поток вводит команду, сервер возвращает около 300 КБ контента.

    Мне нужен метод, который может определить конец вывода на сервер, но я не могу понять это, потому что while( ( bytes = source. Read( buffer ) )!= -1 ) только что заблокирован!

Ответы [ 2 ]

3 голосов
/ 21 января 2011

Ваша программа не имеет char[], поэтому, если вы получаете ошибку OutOfMemoryEr, создающую символ [], он может показать нам строку кода, которая делает это?

Я собирался написать комментарий, но это было слишком долго.

  • Целое приложение будет существовать до тех пор, пока не выйдет последний поток, не являющийся демоном.
  • Вам не нужно устанавливать приоритеты, они обычно не делают то, что вы думаете.
  • Нет смысла прерывать поток, который собирается вернуться / закончить.
  • Вы очищаете, когда читаете ровно 13 байтов, но никогда не отправляете точные 13 байтов.
  • Ваши данные длины 0 не будут получены для блокирующего соединения, но если это произойдет, ваша обработка будет неправильной, и вам будет лучше без нее.
  • copyStream копирует данные до конца потока, однако это происходит в цикле, что означает, что через 10 секунд вы обрежете файл и замените его данными (поскольку поток все еще закрыт и вы не добавляете журнал файл)
  • ваш «читатель» пишет данные, он ничего не читает.
  • ваш поток "писатель" читает и пишет.
  • вы используете Thread напрямую, что считается плохой практикой. Вы должны использовать Runnable и обернуть его потоком или использовать ExecutorService
  • вы ловите несколько исключений, но не распечатываете их. Смело полагать, что вам не нужно знать, когда они брошены, поскольку ваша нить может умереть молча, и вы не будете знать, почему.
  • Я предполагаю, что CopyStreamException является Исключением, и в этом случае вы столкнулись с проблемой создания хорошего исключения для переноса IOException, которое вы позже выбросите.
  • вам не нужно спать между запуском читателя и писателя.
  • , поскольку ваш текущий поток просто ожидает двух других потоков, вы можете использовать текущий поток, чтобы написать «читатель», и иметь только один поток фоновой регистрации.
  • метод не генерирует исключение FileNotFoundException.
  • Вы рассчитываете общее количество скопированных данных, но они никогда не используются.
  • Для копирования потока я предлагаю вам использовать IOUtils.copy (InputStream, OutputStream)
0 голосов
/ 09 апреля 2012

Вы что, утечка из метода copyStream?Как вы это определили?Чтобы повторить начальное утверждение @Peter Lawrey, единственные места в вашем коде, которые ссылаются на char [], это фактические созданные строки.Поскольку ваша программа выполняется в течение длительного времени, всегда создавая строки для обработки и предполагая, что это уникальные строки (т. Е. Не повторно используемые пулом строк JVM, а новая строка, созданная для каждой), что может объяснить использование вашей памяти.Вы можете просто иметь много строк ...

...