2008-08-26 11 views
27

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

ответ

42

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

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

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

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

Документ ВС вы думаете здесь: Java SE 6 HotSpot™ Virtual Machine Garbage Collection Tuning

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

0

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

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

1

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

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

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

4

Не беспокойтесь о финализаторах.

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

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

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

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

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

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

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

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

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

+0

Я бы рассмотрел случай использования для `WeakReference` намного сильнее, чем тот, который используется для` SoftReference`. Если `Foo` нуждается в ссылке на« Bar »для выгоды Foo, он должен использовать сильную ссылку. Если `Foo` нуждается в ссылке на« Bar »for Bar, он должен использовать слабую ссылку. Например, `Bar` может быть уведомлен каждый раз, когда` Foo` что-то делает, но `Foo` был бы так же счастлив, если бы ему не было никому никому никому не нужно. Если единственные ссылки на «Бар» хранятся в вещах, которые на самом деле не заботятся о том, существует ли он, то он не должен существовать. – supercat 2013-05-06 17:05:22

0

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

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

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

3

Насколько финализаторы идут:

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

вы не звоните окончательный метод вообще.

1

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

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

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 («мусор, собирающий поток, который все еще открыт»). Это позволит сэкономить часы и дни отладки и очистки кода после того, как неряшливые любители покинут свои концы.

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

Мир.

 Смежные вопросы

  • Нет связанных вопросов^_^