Почему вы явно не вызываете finalize () или запускаете сборщик мусора? - PullRequest
28 голосов
/ 26 августа 2008

После прочтения этого вопроса мне напомнили, когда меня учили Java, и мне сказали никогда не вызывать finalize () или запускать сборщик мусора, потому что «это большой черный ящик, о котором вам никогда не нужно беспокоиться ». Может ли кто-нибудь свести доводы к этому до нескольких предложений? Я уверен, что мог бы прочитать технический отчет Sun по этому вопросу, но я думаю, что хороший, короткий, простой ответ удовлетворит мое любопытство.

Ответы [ 7 ]

43 голосов
/ 27 августа 2008

Краткий ответ: сборка мусора Java - это очень точно настроенный инструмент. System.gc () - кувалда.

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

Вызов System.gc () напрямую, хотя технически не гарантировано что-либо делать, на практике вызовет дорогостоящий полный сбор куч кучи. Это почти всегда неправильно . Вы думаете, что экономите ресурсы, но на самом деле вы тратите их впустую без веской причины, заставляя Java перепроверять все ваши живые объекты «на всякий случай».

Если у вас возникают проблемы с паузами ГХ в критические моменты, лучше настроить JVM на использование параллельного коллектора меток / разверток, который был разработан специально для минимизации времени, затраченного на паузу, чем пытаться перенести кувалду к проблема и просто сломать его дальше.

Документ Sun, о котором вы думали, находится здесь: Java SE 6 HotSpot & trade; Настройка сбора мусора виртуальной машины

(Еще одна вещь, о которой вы можете не знать: реализация метода finalize () на вашем объекте замедляет сборку мусора. Во-первых, потребуется два прогона GC для сбора объекта: один для запуска finalize () и следующее, чтобы гарантировать, что объект не был воскрешен во время финализации. Во-вторых, объекты с методами finalize () должны рассматриваться GC как особые случаи, потому что они должны собираться индивидуально, их нельзя просто выбросить в навалом.)

4 голосов
/ 27 августа 2008

Что касается финализаторов:

  1. Они практически бесполезны. Не гарантируется, что они будут вызваны своевременно или вообще вообще (если GC никогда не запускается, не будет финализаторов). Это означает, что вы, как правило, не должны полагаться на них.
  2. Финализаторы не гарантированы быть идемпотентными. Сборщик мусора заботится о том, чтобы он никогда не вызывал finalize() более одного раза для одного и того же объекта. С хорошо написанными объектами это не имеет значения, но с плохо написанными объектами многократный вызов finalize может вызвать проблемы (например, двойной выпуск собственного ресурса ... сбой).
  3. Каждый объект , имеющий метод finalize(), должен также предоставлять метод close() (или аналогичный). Это функция, которую вы должны вызывать. например, FileInputStream.close(). Нет смысла вызывать finalize(), когда у вас есть более подходящий метод, который предназначен для , который вы должны вызывать.
4 голосов
/ 27 августа 2008

Не связывайтесь с финализаторами.

Переключиться на добавочную сборку мусора.

Если вы хотите помочь сборщику мусора, аннулируйте ссылки на объекты, которые вам больше не нужны. Меньше пути для следования = более явный мусор.

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

В очень связанном ключе, если вы используете сериализацию и сериализовали временные объекты, вам необходимо очистить кэши сериализации, вызвав ObjectOutputStream.reset (), иначе ваш процесс утечет память и в конце концов умру. Недостатком является то, что непереходные объекты будут повторно сериализованы. Сериализация временных объектов результатов может быть немного более грязной, чем вы думаете!

Рассмотрите возможность использования мягких ссылок. Если вы не знаете, что такое мягкие ссылки, прочитайте javadoc для java.lang.ref.SoftReference

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

Наконец, если вы действительно не можете терпеть GC, используйте Realtime Java.

Нет, я не шучу.

Ссылочная реализация бесплатна для скачивания, и книга Питера Диббла из SUN действительно хороша для чтения.

1 голос
/ 22 марта 2011

Реальная проблема с закрытием дескрипторов ОС при финализации заключается в том, что финализация выполняется в не гарантированном порядке. Но если у вас есть дескрипторы того, что блокирует (например, сокеты), потенциально ваш код может попасть в тупиковую ситуацию (совсем не тривиально).

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

SomeStream s = null;
...
try{
   s = openStream();
   ....
   s.io();
   ...
} finally {
   if (s != null) {
       s.close();
       s = null;
   }
}

Это становится еще сложнее, если вы пишете свои собственные классы, которые работают через JNI и открытые дескрипторы. Вы должны убедиться, что ручки закрыты (отпущены) и что это произойдет только один раз. Часто забываемый дескриптор ОС в Desktop J2SE - Graphics[2D]. Даже BufferedImage.getGrpahics() потенциально может вернуть вам дескриптор, который указывает на видеодрайвер (фактически удерживая ресурс на GPU). Если вы не выпустите его самостоятельно и не оставите сборщик мусора для выполнения работы - вы можете столкнуться со странной ситуацией OutOfMemory и тому подобным, когда у вас закончились растровые изображения, отображенные на видеокарте, но при этом все еще достаточно памяти. По моему опыту, это довольно часто случается в тесных циклах, работающих с графическими объектами (извлечение миниатюр, масштабирование, повышение резкости, как вы его называете).

В основном GC не берет на себя ответственность программистов за правильное управление ресурсами. Это только заботится о памяти и ничего больше. Вызов Stream.finalize close () IMHO был бы лучше реализован, создав исключение new RuntimeError («сборщик мусора для потока, который все еще открыт»). Это сэкономит часы и дни отладки и очистки кода после того, как неаккуратные любители оставят концы проигравшими.

Удачного кодирования.

Мир.

1 голос
/ 26 августа 2008

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

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

Если вы знали больше о том, что GC имеет внутри, то вы могли бы принимать более обоснованные решения, но тогда вы упустили преимущества GC.

0 голосов
/ 27 августа 2008

Избегайте финализаторов. Нет гарантии, что они будут вызваны своевременно. Это может занять довольно много времени, прежде чем система управления памятью (то есть сборщик мусора) решит собрать объект с помощью финализатора.

Многие люди используют финализаторы для таких вещей, как закрытие сокетных соединений или удаление временных файлов. Тем самым вы делаете поведение вашего приложения непредсказуемым и привязанным к тому, когда JVM собирается собирать ваш объект. Это может привести к сценариям «нехватки памяти» не из-за исчерпания кучи Java, а из-за того, что в системе не хватает дескрипторов для конкретного ресурса.

Еще одна вещь, которую нужно иметь в виду, заключается в том, что введение вызовов System.gc () или подобных молотков может показать хорошие результаты в вашей среде, но они не обязательно будут переводиться на другие системы. Не все используют одну и ту же JVM, их много: SUN, IBM J9, BEA JRockit, Harmony, OpenJDK и т. Д. Все эти JVM соответствуют JCK (те, которые были официально протестированы, но это свобода, когда дело доходит до быстроты. GC является одной из тех областей, в которые каждый вкладывает значительные средства. Использование молотка часто разрушает это усилие.

0 голосов
/ 26 августа 2008

GC проводит большую оптимизацию в отношении того, когда правильно завершать работу.

Поэтому, если вы не знакомы с тем, как на самом деле работает GC и как он маркирует поколения, ручной вызов finalize или start GC'ing, скорее всего, снизит производительность, чем поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...