Подтвердить что ты не робот

Этот пример Java вызывает утечку памяти?

У меня есть простой пример. В примере загружается ArrayList<Integer> из файла f, содержащего 10000000 случайных чисел.

doLog("Test 2");
{
    FileInputStream fis = new FileInputStream(f);
    ObjectInputStream ois = new ObjectInputStream(fis);
    List<Integer> l = (List<Integer>) ois.readObject();
    ois.close();
    fis.close();
    doLog("Test 2.1");
    //l = null; 
    doLog("Test 2.2");
}
doLog("Test 2.3");
System.gc();
doLog("Test 2.4");

Когда у меня есть l = null, я получаю этот журнал:

Test 2                          Used Mem = 492 KB   Total Mem = 123 MB
Test 2.1                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.2                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.3                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.4                        Used Mem = 493 KB   Total Mem = 123 MB

Но когда я его удалю, я получаю этот журнал вместо этого.

Test 2                          Used Mem = 492 KB   Total Mem = 123 MB
Test 2.1                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.2                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.3                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.4                        Used Mem = 44 MB    Total Mem = 123 MB

Used Memory рассчитывается по формуле: runTime.totalMemory() - runTime.freeMemory()

Вопрос: Если присутствует l = null;, есть ли утечка памяти? l недоступен, поэтому почему он не может быть освобожден?

4b9b3361

Ответ 1

В приведенном выше коде отсутствует утечка памяти.

Как только вы покидаете блок кода, заключенный в {}, переменная l выходит из области видимости, а List является кандидатом на сборку мусора, независимо от того, установлена ​​ли она на null сперва или нет.

Однако, после блока кода и до возврата метода, List находится в состоянии, называемом невидимым. Хотя это верно, JVM вряд ли автоматически удалит ссылку и соберет память List. Поэтому явная настройка l = null может помочь JVM собрать память перед выполнением вычислений памяти. В противном случае это произойдет автоматически, когда метод вернется.

Вероятно, вы получите разные результаты для разных прогонов кода, так как вы точно не знаете, когда будет запускаться сборщик мусора. Вы можете предположить, что вы думаете, что он должен запускаться с использованием System.gc() (и он может даже собирать невидимый List даже без установки l = null), но нет promises. Это указано в javadoc для System.gc():

Вызов метода gc предполагает, что виртуальная машина Java расходует усилия по утилизации неиспользуемых объектов, чтобы сделать память они в настоящее время доступны для быстрого повторного использования. Когда управление возвращается от вызова метода виртуальная машина Java приложила все усилия чтобы освободить пространство от всех отброшенных объектов.

Ответ 2

Я думаю, здесь есть немного семантики. "Утечка памяти" обычно означает, что некоторые данные хранятся в памяти программой (частью программного обеспечения и т.д.) И получают эту программу в состояние, когда она больше не может получить доступ к данным в памяти, чтобы очистить их, тем самым попадая в ситуацию где эта память не может быть заявлена ​​для будущего использования. Это, насколько я могу судить, является общим определением.

Использование термина "утечка памяти" в реальном мире обычно относится к языкам программирования, где разработчик может вручную выделять память для данных, которые он намеревается разместить в куче. Такими языками являются C, С++, Objective-C (*) и т.д. Например, команда "malloc" или "новый" оператор выделяют память для экземпляра класса, который будет помещен в пространство памяти кучи. В таких языках указатель должен храниться в тех выделенных таким образом экземплярах, если позже мы захотим очистить используемую ими память (когда они больше не нужны). Продолжая в приведенном выше примере, указатель, ссылающийся на экземпляр, созданный в куче с использованием "нового", впоследствии может быть "удален" из памяти с помощью команды "удалить" и передать ему указатель в качестве параметра.

Таким образом, для таких языков утечка памяти обычно означает наличие данных, размещенных на куче, а затем следующего:

  • приходя в состояние, в котором больше нет указателя на эти данные или
  • забыть/игнорировать вручную "де-распределение" данных на куче (через указатель)

Теперь, в контексте такого определения "утечки памяти", это практически не может произойти с Java. Технически, в Java это задача сборщика мусора решить, когда экземпляры, выделенные кучей, больше не ссылаются или не выходят из сферы действия и не очищают их. Там нет такого эквивалента команды "delete" С++ в Java, которая бы даже позволяла разработчику вручную "де-распределять" экземпляры/данные из кучи. Даже создание всех указателей экземпляра null не приведет к немедленному освобождению памяти этого экземпляра, но вместо этого оно сделает его "сборкой мусора", оставив его в потоке коллекционера мусора, чтобы очистить его, когда он делает свои развертки.

Теперь еще одна вещь, которая может случиться в Java, - это никогда не отпускать указатели на определенные экземпляры, даже если они больше не понадобятся после определенной точки. Или, чтобы дать определенному экземпляру область, которая слишком велика для того, что они используются. Таким образом, они будут держаться в памяти дольше, чем нужно (или навсегда, где навсегда означает, пока процесс JDK не будет убит), и, таким образом, они не будут собраны сборщиком мусора, хотя с функциональной точки зрения их следует очистить. Это может привести к поведению, подобному "утечке памяти" в более широком смысле, где "утечка памяти" просто означает "наличие материала в памяти, когда он больше не нужен и не имеет возможности его очистить".

Теперь, как вы можете видеть, "утечка памяти" несколько расплывчата, но из того, что я вижу, ваш пример не содержит утечки памяти (даже версии, где вы не делаете l = null). Все ваши переменные находятся в узкой области действия, которые ограничены блоком accolade, они используются внутри этого блока и будут выпадать из области действия, когда заканчивается блок, таким образом, они будут собираться "правильно" (с функциональной точки ваша программа). Поскольку @Keppil заявляет: включение нулевого указателя даст GC более точное представление о том, когда очистить соответствующий экземпляр, но даже если вы никогда не сделаете его нулевым, ваш код не будет (необязательно) зависать с экземплярами, поэтому там нет утечки памяти.

Типичным примером утечки памяти Java является использование кода, развернутого на сервере приложений Java EE, так что он будет порождать потоки вне контроля упомянутого сервера приложений (изображение сервлета, запускающего задание кварца). Если приложение разворачивается и не развертывается несколько раз, возможно, что некоторые из потоков не будут убиты во время undeploy, но также (повторно) запущены во время развертывания, таким образом оставив их и любые экземпляры, которые они могли бы создать, бесполезно повесить в памяти.

(*) Более поздние версии Objective-C также дают возможность управлять памятью кучи автоматически, подобно механизму сборки мусора Javas.

Ответ 3

Реальный ответ заключается в том, что, если код не JIT, все локальные переменные "достижимы" внутри тела метода.

Более того, фигурные скобки в байт-коде ничего не делают. Они существуют только на исходном уровне - JVM абсолютно не знает о них. Установка l на null эффективно освобождает ссылку вверх от стека, поэтому она GC'd для реального. Счастливые вещи.

Если вы использовали другой метод вместо встроенного блока, все прошло бы без сюрпризов.

Если код JIT'd и JVM-компилятор построил определения достижений (также это), скорее всего установка l=null не будет иметь эффекта, и память будет освобождена в любом случае.

Ответ 4

Вопрос: В случае удаления l = null; (не имеют этой строки код), является ли это утечкой памяти?

Нет, но это облегчает gc в запросе памяти, если вы делаете этот "шаблон"