Почему вы явно не вызываете метод Finalize() или не запускаете сборщик мусора?

StackOverflow https://stackoverflow.com/questions/28949

  •  09-06-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

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

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

Эталонную реализацию можно загрузить бесплатно, а книгу Питера Дибблса из SUN действительно полезно прочитать.

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

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

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

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

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

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

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

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

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

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

Приятного кодирования.

Мир.

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top