Почему плохая практика вызывать System.gc ()? - PullRequest
308 голосов
/ 10 марта 2010

После ответа на вопрос о том, как принудительно освободить объекты в Java (парень очищал HashMap 1,5 ГБ) с помощью System.gc(), мне сказали, что это плохая практика позвоните System.gc() вручную, но комментарии были не совсем убедительными. Кроме того, никто, похоже, не осмеливался ни отрицать, ни отрицать мой ответ.

Мне там сказали, что это плохая практика, но потом мне также сказали, что сборщики мусора систематически больше не останавливают мир, и что JVM также может эффективно использовать его только в качестве подсказки, поэтому я ' м в убыток.

Я понимаю, что JVM обычно лучше вас знает, когда ему нужно восстановить память. Я также понимаю, что беспокоиться о нескольких килобайтах данных глупо. Я также понимаю, что даже мегабайты данных - это не то, что было несколько лет назад. Но все же 1,5 гигабайта? И вы знаете, как будто 1,5 ГБ данных хранятся в памяти; это не похоже на выстрел в темноте. System.gc() систематически плохо или есть какой-то момент, когда все становится хорошо?

Итак, вопрос на самом деле двойной:

  • Почему называть System.gc() плохой или плохой практикой? Действительно ли это просто подсказка JVM при определенных реализациях, или это всегда полный цикл сбора? Есть ли на самом деле реализации сборщика мусора, которые могут выполнять свою работу, не останавливая мир? Просьба пролить свет на различные утверждения, сделанные людьми в комментариях к моему ответу .
  • Где порог? никогда - это хорошая идея для звонка System.gc(), или бывают случаи, когда это приемлемо? Если да, то что это за времена?

Ответы [ 13 ]

235 голосов
/ 10 марта 2010

Причина, по которой все всегда говорят избегать System.gc(), заключается в том, что это довольно хороший показатель принципиально неработающего кода . Любой код, который зависит от него для правильности, безусловно, нарушен; все, что зависит от производительности, скорее всего сломаны.

Вы не знаете, под каким сборщиком мусора вы работаете. Конечно, есть такие, которые не «останавливают мир» , как вы утверждаете, но некоторые JVM не настолько умны или по разным причинам (возможно, они находятся на телефоне?) Не делают этого. Вы не знаете, что он собирается делать.

Кроме того, ничего не гарантируется. JVM может просто полностью игнорировать ваш запрос.

Сочетание «вы не знаете, что это будет делать», «вы не знаете, поможет ли это вообще» и «вам не нужно так называть это», - вот почему люди так сильны в Говоря, что, как правило, вы не должны называть это. Я думаю, что это тот случай, когда «если вам нужно спросить, следует ли вам использовать это, вы не должны»


РЕДАКТИРОВАТЬ , чтобы устранить некоторые проблемы из другого потока:

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

Как и в случае JVM будет хранить память (много десятков мегабайт) и увеличивать кучу по мере необходимости. Он не обязательно возвращает эту память в систему, даже когда вы освобождаете объекты Java; он совершенно свободен для хранения выделенной памяти для использования в будущих распределениях Java.

Чтобы показать, что System.gc() ничего не делает, просмотрите:

http://bugs.sun.com/view_bug.do?bug_id=6668279

и, в частности, есть опция -XX: DisableExplicitGC VM.

144 голосов
/ 10 марта 2010

Уже было объяснено, что вызов system.gc() может ничего не делать, и что любой код, которому "нужен" сборщик мусора для запуска, не работает.

Однако прагматическая причина называть System.gc() плохой практикой заключается в том, что она неэффективна. И в худшем случае это ужасно неэффективно ! Позвольте мне объяснить.

Типичный алгоритм GC идентифицирует мусор путем обхода всех объектов без мусора в куче и выведения, что любой объект, который не был посещен, должен быть мусором. Исходя из этого, мы можем смоделировать общую работу сборки мусора, состоящую из одной части, которая пропорциональна количеству активных данных, и другой части, которая пропорциональна количеству мусора; т.е. work = (live * W1 + garbage * W2).

Теперь предположим, что вы делаете следующее в однопоточном приложении.

System.gc(); System.gc();

Первый вызов (мы предсказываем) сработает (live * W1 + garbage * W2) и избавит от оставшегося мусора.

Второй звонок будет (live* W1 + 0 * W2) работать и ничего не восстанавливать. Другими словами, мы проделали (live * W1) работу, а абсолютно ничего не достигли .

Мы можем смоделировать эффективность коллектора как объем работы, необходимой для сбора единицы мусора; то есть efficiency = (live * W1 + garbage * W2) / garbage. Таким образом, чтобы сделать GC максимально эффективным, нам нужно максимизировать значение garbage, когда мы запускаем GC; т.е. ждать, пока куча не заполнится. (А также, сделайте кучу максимально большой. Но это отдельная тема.)

Если приложение не вмешивается (вызывая System.gc()), GC будет ждать, пока куча не заполнится, прежде чем запускаться, что приведет к эффективному сбору мусора 1 . Но если приложение заставляет GC работать, есть вероятность, что куча не будет заполнена, и в результате мусор будет собираться неэффективно. И чем чаще приложение вызывает GC, тем более неэффективным становится GC.

Примечание: приведенное выше объяснение скрывает тот факт, что типичный современный GC разделяет кучу на «пробелы», GC может динамически расширять кучу, рабочий набор приложения, не содержащий мусора, может различаться и т. Тем не менее, один и тот же базовый принцип применим ко всем истинным сборщикам мусора 2 . Принудительно запускать GC неэффективно.


1 - так работает коллектор «пропускная способность». Параллельные сборщики, такие как CMS и G1, используют разные критерии, чтобы решить, когда запускать сборщик мусора.

2 - Я также исключаю менеджеры памяти, которые используют исключительно подсчет ссылок, но ни одна из существующих реализаций Java не использует этот подход ... по уважительной причине.

43 голосов
/ 14 марта 2010

Многие люди, кажется, говорят вам не делать этого. Я не согласен. Если после большого процесса загрузки, такого как загрузка уровня, вы считаете, что:

  1. У вас есть много объектов, которые недоступны и, возможно, не были получены. и
  2. Вы думаете, что пользователь может смириться с небольшим замедлением в этот момент

Нет никакого вреда при вызове System.gc (). Я смотрю на это как ключевое слово c / c ++ inline. Это просто намек на то, что вы, разработчик, решили, что время / производительность не так важны, как обычно, и что некоторые из них могут быть использованы для восстановления памяти.

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

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

29 голосов
/ 10 марта 2010

Люди хорошо поработали, объяснив, почему НЕ использовать, поэтому я расскажу вам пару ситуаций, когда вы должны его использовать:

(Следующие комментарии относятся к Hotspot, работающему в Linux с коллектором CMS, где я уверен, говоря, что System.gc() на самом деле всегда вызывает полную сборку мусора).

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

  2. В том же ключе, что и в # 1, если вы внимательно следите за использованием кучи, вы хотите точно определить, каков базовый объем используемой памяти. Если первые 2 минуты времени безотказной работы вашего приложения полностью инициализируются, ваши данные будут испорчены, если вы не заставите (хм ... «предложить») полный сбор данных.

  3. У вас может быть приложение, предназначенное для того, чтобы никогда не рекламировать что-либо для постоянного поколения, пока оно работает. Но, возможно, вам нужно заранее инициализировать некоторые данные, которые не настолько велики, чтобы автоматически переходить к постоянному поколению. Если вы не вызовете System.gc () после того, как все настроено, ваши данные могут находиться в новом поколении, пока не придет время для их продвижения. Внезапно ваше сверхпопулярное приложение с малой задержкой и низким ГХ получает огромный (условно говоря) штраф за задержку за продвижение этих объектов во время обычных операций.

  4. Иногда полезно иметь доступ к System.gc в производственном приложении для проверки наличия утечки памяти. Если вы знаете, что набор живых данных в момент времени X должен существовать в определенном соотношении с набором живых данных в момент времени Y, то было бы полезно вызвать System.gc () для времени X и времени Y и сравнить использование памяти. .

9 голосов
/ 10 марта 2010

Эффективность ГХ зависит от ряда эвристик. Например, распространенная эвристика заключается в том, что доступ на запись к объектам обычно происходит на объектах, которые были созданы не так давно. Другое - то, что многие объекты очень недолговечны (некоторые объекты будут использоваться в течение длительного времени, но многие будут отброшены через несколько микросекунд после их создания).

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

Для надежного использования System.gc() (*) вам необходимо знать, как работает ГХ, во всех его мелких деталях. Такие детали могут немного измениться, если вы используете JVM от другого поставщика, или следующую версию от того же поставщика, или ту же JVM, но с немного другими параметрами командной строки. Так что это редко хорошая идея, если только вы не хотите решить конкретную проблему, в которой вы контролируете все эти параметры. Отсюда и понятие «плохой практики»: это не запрещено, метод существует, но он редко окупается.

(*) Я говорю об эффективности здесь. System.gc() никогда не будет нарушать правильную программу Java. Он также не вызовет дополнительной памяти, которую JVM не могла бы получить иначе: перед броском OutOfMemoryError JVM выполняет работу System.gc(), даже если в качестве последнего средства.

7 голосов
/ 29 сентября 2014

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

Много лет назад я работаю над генератором отчетов, который

  • Была одна нить
  • Считать «запрос отчета» из очереди
  • Загружены данные, необходимые для отчета из базы данных
  • Сгенерировал отчет и отправил его по электронной почте.
  • Повторяется вечно, спит, когда не было невыполненных запросов.
  • Он не использовал повторно данные между отчетами и не делал никакого кеширования.

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

Глядя на вышеприведенную схему процесса, становится ясно, что.

  • Мы знаем, что сразу после отправки отчета будет очень мало живых объектов, поскольку следующий запрос еще не начал обрабатываться.
  • Хорошо известно, что стоимость запуска цикла сборки мусора зависит от количества живых объектов, количество мусора мало влияет на стоимость запуска GC.
  • Что, когда очередь пуста, нет ничего лучше, чем запустить GC.

Следовательно, очевидно, что выполнение GC всегда стоило, когда очередь запросов была пуста; в этом не было недостатка.

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

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

Мы никогда не обнаруживали установку, которая не приносила пользы - принудительный запуск GC каждый раз, когда рабочая очередь была пустой.

Но, позвольте прояснить, вышесказанное не является частым случаем.

6 голосов
/ 30 июня 2016

Это очень утомительный вопрос, и я чувствую, что многие противостоят Java, несмотря на то, насколько он полезен для языка.

Тот факт, что вы не можете доверять «System.gc» делать что-либо, невероятно пугающий и может легко вызвать чувство «страха, неуверенности, сомнения» в языке.

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

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

Позвольте мне рассмотреть аргументы этой темы.

  1. Неэффективно:

Часто программа может ничего не делать, и вы знаете, что она ничего не делает из-за того, как она была задумана. Например, он может делать какое-то долгое ожидание с большим окном сообщения ожидания, и в конце он может также добавить вызов для сбора мусора, потому что время его выполнения займет действительно небольшую часть времени долгое ожидание, но gc не будет появляться в середине более важной операции.

  1. Это всегда плохая практика и указывает на неработающий код.

Я не согласен, не имеет значения, какой у вас сборщик мусора. Его работа состоит в том, чтобы отслеживать мусор и убирать его.

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

Конечно, он может вести себя не так, как вы хотите или ожидаете, но когда вы действительно хотите позвонить, вы знаете, что ничего не происходит, и пользователь готов терпеть медлительность / время простоя. Если System.gc работает, отлично! Если это не так, по крайней мере, вы пытались. Там просто нет обратной стороны, если сборщик мусора не имеет внутренних побочных эффектов, которые делают что-то ужасно неожиданное для того, как сборщик мусора ведет себя, если вызывается вручную, и это само по себе вызывает недоверие.

  1. Это не обычный случай использования:

Это вариант использования, который не может быть достигнут надежно, но мог бы быть, если система была спроектирована таким образом. Это все равно, что сделать светофор и сделать так, чтобы некоторые / все кнопки светофора ничего не делали, это заставляет вас задаться вопросом, почему кнопка существует для начала, в javascript нет функции сбора мусора, поэтому мы Тщательно изучите его.

  1. В спецификации сказано, что System.gc () является подсказкой, что GC должен работать, а виртуальная машина может игнорировать ее.

что такое "подсказка"? что такое "игнорировать"? компьютер не может просто получать подсказки или игнорировать что-то, существуют строгие поведенческие пути, которые могут быть динамическими и руководствуются намерениями системы. Правильный ответ будет включать то, что фактически делает сборщик мусора на уровне реализации, что заставляет его не выполнять сбор, когда вы запрашиваете его. Эта функция просто нет? Есть ли какие-то условия, которые я должен выполнить? Каковы эти условия?

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

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

Подводя итог, существует неотъемлемая проблема с ответами: эта функция может ничего не делать, и я не буду вдаваться в подробности, как определить, когда она что-то делает, а когда нет и почему она не будет или будет часто подразумевать, что это просто против философии пытаться сделать это, даже если намерения, стоящие за этим, разумны ".

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

3 голосов
/ 10 марта 2010

Может быть, я пишу дрянной код, но я осознал, что нажатие на значок корзины в IDE затмения и NetBeans является «хорошей практикой».

2 голосов
/ 02 сентября 2013

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

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

Один из примеров, приведенных здесь, попал в мусорную корзину в вашей IDE. Если вы отправляетесь на встречу, почему бы не ударить ее. Накладные расходы не повлияют на вас, и куча может быть очищена, когда вы вернетесь. Сделайте это в производственной системе, и частые звонки на сбор приведут к полной остановке! Даже случайные вызовы, например, сделанные RMI, могут повлиять на производительность.

1 голос
/ 10 марта 2010

Да, вызов System.gc () не гарантирует его выполнения, это запрос к JVM, который может быть проигнорирован. Из документов:

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

Почти всегда плохая идея называть это, потому что автоматическое управление памятью, как правило, знает лучше, чем вы, когда gc. Это будет происходить при низком внутреннем пуле свободной памяти или при запросе операционной системы некоторой памяти.

Возможно, будет приемлемо вызвать System.gc (), если вы знаете, , что это помогает. Под этим я подразумеваю, что вы тщательно протестировали и измерили поведение обоих сценариев на платформе развертывания , и вы можете показать, что это помогает. Имейте в виду, что gc не так легко предсказать - он может помочь при одном запуске и навредить при другом.

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