Утечка ресурсов: "in" никогда не закрывается, хотя закрыт

Я знаю, что есть пара аналогично названных вопросов, но большинство из них просто забыли поставить директиву close() на свой поток. Это другое.

Допустим, у меня есть следующий минимальный пример:

public void test() throws IOException
{
    InputStream in;
    if( file.exists() )
    {
        in = new FileInputStream( file );
    }
    else
    {
        in = new URL( "some url" ).openStream();
    }
    in.close();
}

Это даст мне предупреждение Resource leak: 'in' is never closed в Eclipse (Juno SR1). Но когда я перемещаю in.close() в условный блок, предупреждения исчезают:

public void test() throws IOException
{
    InputStream in;
    if( file.exists() )
    {
        in = new GZIPInputStream( new FileInputStream( file ) );
        in.close();
    }
    else
    {
        in = new URL( "some URL" ).openStream();
    }
}

Что здесь происходит?

Ответ 1

Вот как я его напишу:

public void test() throws IOException
{
    InputStream in = null;
    try {
        if(file.exists()) {
            in = new FileInputStream( file );
        } else {
            in = new URL( "some url" ).openStream();
        }
        // Do something useful with the stream.
    } finally {
        close(in);
    }
}

public static void close(InputStream is) {
    try {
        if (is != null) {
            is.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Ответ 2

Из-за исключения IO вы можете столкнуться с утечкой ресурсов (по-настоящему)

Попробуйте сделать следующее:

public void test() throws IOException
{
    InputStream in= null;
    try {
        if( file.exists() )
        {
            // In this case, if the FileInputStream call does not
            // throw a FileNotFoundException (descendant of IOException)
            // it will create the input stream which you are wrapping
            // in a GZIPInputStream (no IO exception on construction)
            in = new GZIPInputStream( new FileInputStream( file ) );
        }
        else
        {
            // Here however, if you are able to create the URL
            // object, "some url" is a valid URL, when you call
            // openStream() you have the potential of creating
            // the input stream. new URL(String spec) will throw
            // a MalformedURLException which is also a descendant of
            // IOException.
            in = new URL( "some url" ).openStream();
        }

        // Do work on the 'in' here 
    } finally {
        if( null != in ) {
            try 
            {
                in.close();
            } catch(IOException ex) {
                // log or fail if you like
            }
        }
    }
}

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

В исходном коде у вас был объявлен InputStream, но он не был инициализирован. Для начала это плохая форма. Инициализируйте это значение как null, как показано выше. Мое чувство, и на данный момент я не запускаю Юнону, это то, что он видит, что InputStream 'in', может потенциально пройти через все обручи и препятствия, чтобы добраться до точки, в которой вы собираетесь ее использовать. К несчастью, как заметил кто-то, ваш код немного изволен для примера. Выполняя это, как я подробно описал, а также @duffymo, вы избавитесь от предупреждения.

Ответ 3

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

Ответ 4

Поток не может быть инициализирован, если файл не существует, и вы пытаетесь закрыть несуществующий файл.

В вашем втором примере также потребуется закрыть инструкцию, чтобы избежать утечек.

Ответ 5

Эта же отчетность Eclipse может произойти, когда вы явно бросаете исключение после того, как вы открыли свой ресурс, например:

public void method() throws IOException {
   BufferedReader br = new BufferedReader(new FileReader("myfile.txt"));
   while (br.ready()) {
      String line = br.readLine():
      if (line.length() > 255) {
         throw new IOException("I am some random IOException");
      }
   }
   br.close();
}

Это какой-то надуманный код для демонстрационных целей, поэтому не выглядите слишком сложно.

Если кто-то должен был прокомментировать строку, предупреждение исчезнет. Конечно, вы хотите убедиться, что этот ресурс закрыт должным образом. Вы можете сделать:

if (line.length() > 255) {
   br.close();
   throw new IOException("I am some random IOException");
}

Не полагайтесь на предупреждения Eclipse в этом случае. Получите привычку использовать метод try/finally, чтобы убедиться, что ресурсы правильно и последовательно закрыты.

Ответ 6

У меня есть что-то вроде:

InputStream content = httpResponse.getEntity()==null?null:httpResponse.getEntity().getContent();

который дает тот же самый warrning. Но если я оставлю это так:

InputStream content =httpResponse.getEntity().getContent();

Я не получаю никаких обвинений. Не странно или что?

- Я надеюсь, что моя информация добавит знания в исходный вопрос. Спасибо!