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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

Не утруждайтесь финализаторами.

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

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

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

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

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

Следите за ссылками Phantom и слабыми ссылками, если вы действительно не возбудите.

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

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

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

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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

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

Счастливое кодирование.

Мир.

Ответ 6

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

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

Ответ 7

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

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

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

Ответ 8

вы вообще не вызываете метод finalize.