Улучшает ли использование final для переменных в Java сборку мусора?

86

Сегодня мы с коллегами обсуждаем использование finalключевого слова в Java для улучшения сборки мусора.

Например, если вы напишете такой метод:

public Double doCalc(final Double value)
{
   final Double maxWeight = 1000.0;
   final Double totalWeight = maxWeight * value;
   return totalWeight;  
}

Объявление переменных в методе finalпоможет сборщику мусора очистить память от неиспользуемых переменных в методе после выхода из метода.

Это правда?

Горан Мартинич
источник
на самом деле здесь есть две вещи. 1) когда вы пишете в локальное поле метода и экземпляр. Когда вы пишете в экземпляр, это может быть полезно.
Евгений

Ответы:

86

Вот немного другой пример, с конечными полями ссылочного типа, а не с конечными локальными переменными типа значения:

public class MyClass {

   public final MyOtherObject obj;

}

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

JVM использует алгоритм GC mark-sweep, который должен проверять все активные ссылки в «корневых» местоположениях GC (как и все объекты в текущем стеке вызовов). Каждый живой объект «помечается» как живой, и любой объект, на который ссылается живой объект, также помечается как живой.

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

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

Итак, мысленно я скажу: «Нет,« последний »модификатор не помогает сборщику мусора снизить его рабочую нагрузку».

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

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

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

Benjismith
источник
Пища для размышлений. Я всегда думал, что встроенный код работает быстрее, но если jvm не хватает памяти, он тоже будет медленнее. хммммм ...
WolfmanDragon
1
Я просто предполагаю здесь ... но я предполагаю, что JIT-компилятор может встроить окончательные примитивные значения (не объекты) для умеренного увеличения производительности. С другой стороны, встраивание кода может привести к значительной оптимизации, но не имеет ничего общего с переменными final.
benjismith
2
Невозможно присвоить null уже созданному объекту final, тогда, возможно, final вместо help, может усложнить задачу
Эрнан Эче
1
Можно также использовать {} для ограничения области видимости в большом методе, а не разбивать ее на несколько частных методов, которые могут не иметь отношения к другим методам класса.
ммм
Вы также можете использовать локальные переменные вместо полей, когда это возможно, чтобы улучшить сборку мусора памяти и уменьшить ссылочные связи.
sivi
37

Объявление локальной переменной finalне повлияет на сборку мусора, это только означает, что вы не можете изменить переменную. Приведенный выше пример не должен компилироваться, поскольку вы изменяете totalWeightотмеченную переменную final. С другой стороны, объявление примитива ( doubleвместо Double) finalпозволит встроить эту переменную в вызывающий код, что может вызвать некоторое улучшение памяти и производительности. Это используется, когда public static final Stringsв классе есть несколько человек.

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

Аарон
источник
26

Нет, это категорически не так.

Помните, что finalэто не означает постоянство, это просто означает, что вы не можете изменить ссылку.

final MyObject o = new MyObject();
o.setValue("foo"); // Works just fine
o = new MyObject(); // Doesn't work.

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

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

SCdF
источник
17

Некоторые моменты, которые необходимо прояснить:

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

  • В Java пока нет распределения внутри стека.

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

Кирк
источник
В java есть выделение в стеке (примитивов и ссылок на объекты в куче): stackoverflow.com/a/8061692/32453 Java требует, чтобы объекты в том же закрытии, что и анонимный класс / лямбда, также были окончательными, но превращается это просто для уменьшения «требуемой памяти / фрейма» / уменьшения путаницы, так что это не связано с ее сбором ...
rogerdpack
11

Что ж, я не знаю об использовании модификатора final в этом случае или его влиянии на сборщик мусора.

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

Я использую упакованные примитивы только тогда, когда это требуется для существующего API, или когда мне нужны примитивы, допускающие значение NULL.

Benjismith
источник
1
Ты прав. Мне нужен был только быстрый пример, чтобы объяснить мой вопрос.
Горан Мартинич
5

Конечные переменные не могут быть изменены после начального присвоения (выполняется компилятором).

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

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

final boolean debug = false;

......

if (debug) {
  System.out.println("DEBUG INFO!");
}

Println не будет включен в байтовый код.

Торбьёрн Равн Андерсен
источник
@Eugene Зависит от вашего менеджера безопасности и от того, встроил ли компилятор переменную или нет.
Thorbjørn Ravn Andersen
да, я просто был педантичен; ничего более; тоже дал ответ
Евгений
4

Есть не очень известный случай с поколенными сборщиками мусора. (Краткое описание читайте в ответе Бенджисмита. для более глубокого понимания читайте статьи в конце).

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

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

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

Конечно, как указывали многие, ключевое слово final не влияет на сборщик мусора, но оно гарантирует, что ссылка никогда не будет изменена на более молодой объект, если этот объект переживет второстепенные коллекции и перейдет в более старую кучу.

Статьи:

IBM о сборке мусора: история , JVM в горячей точке и производительность . Возможно, они уже не полностью действительны, поскольку датируются 2003/04 годом, но они дают легкую для чтения информацию о сборщиках мусора.

Солнце о настройке сборки мусора

Давид Родригес - дрибеас
источник
3

GC работает с недоступными ссылками. Это не имеет ничего общего с «финалом», который является просто утверждением одноразового задания. Возможно ли, что некоторые виртуальные машины GC могут использовать "final"? Я не понимаю, как и почему.

Донгилмор
источник
3

finalдля локальных переменных и параметров не имеет значения для создаваемых файлов классов, поэтому не может влиять на производительность во время выполнения. Если у класса нет подклассов, HotSpot в любом случае рассматривает этот класс как окончательный (он может отменить позже, если загружен класс, который нарушает это предположение). Я считаю, что finalметоды во многом похожи на классы. finalв статическом поле может позволить интерпретировать переменную как «постоянную времени компиляции», и на этой основе javac будет выполнять оптимизацию. finalon fields дает JVM некоторую свободу игнорировать отношения " происходит до" .

Том Хотин - tackline
источник
2

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

Ответ на ваш вопрос категорический нет.

Мэтт Куигли
источник
Это может быть правдой, но компилятор все еще может использовать окончательную информацию во время анализа потока данных.
@WernerVanBelle компилятор уже знает, что переменная устанавливается только один раз. Он должен уже провести анализ потока данных, чтобы знать, что переменная может иметь значение NULL, не инициализирована перед использованием и т. Д. Таким образом, локальные финалы не предлагают компилятору никакой новой информации.
Мэтт Куигли
Это не так. Анализ потока данных может вывести множество вещей, но можно иметь полную программу по Тьюрингу внутри блока, которая будет или не будет устанавливать локальную переменную. Компилятор не может заранее знать, будет ли переменная записана и будет ли она вообще постоянной. Поэтому компилятор без ключевого слова final не может гарантировать, что переменная является окончательной или нет.
@WernerVanBelle Я искренне заинтригован, вы можете привести пример? Я не понимаю, как может быть окончательное присвоение не конечной переменной, о которой компилятор не знает. Компилятор знает, что если у вас есть неинициализированная переменная, он не позволит вам ее использовать. Если вы попытаетесь назначить конечную переменную внутри цикла, компилятор вам не позволит. Каков пример, когда переменная МОЖЕТ быть объявлена ​​окончательной, но НЕ является окончательной, и компилятор не может гарантировать, что она является окончательной? Я подозреваю, что любой пример будет переменной, которую нельзя объявить окончательной.
Мэтт Куигли
А, перечитав ваш комментарий, я вижу, что ваш пример - это переменная, инициализированная внутри условного блока. Эти переменные не могут быть окончательными, если все пути условий не инициализируют переменную один раз. Итак, компилятор знает о таких объявлениях - вот как он позволяет вам скомпилировать конечную переменную, инициализированную в двух разных местах (подумайте final int x; if (cond) x=1; else x=2;). Таким образом, компилятор без ключевого слова final может гарантировать, является ли переменная окончательной или нет.
Мэтт Куигли
1

Все методы и переменные могут быть переопределены по умолчанию в подклассах. Если мы хотим сохранить подклассы от переопределения членов суперкласса, мы можем объявить их как final с помощью ключевого слова final. Например, final int a=10; final void display(){......} создание метода final гарантирует, что функциональность, определенная в суперклассе, никогда не будет изменена. Точно так же нельзя изменить значение конечной переменной. Конечные переменные ведут себя как переменные класса.

Дебасиш Пахира
источник
1

Строго говоря о полях экземпляра , final может немного улучшить производительность, если конкретный сборщик мусора захочет это использовать. Когда происходит одновременное выполнение GC(это означает, что ваше приложение все еще работает, пока выполняется сборщик мусора ), см. Это для более широкого объяснения , сборщики мусора должны использовать определенные барьеры при выполнении записи и / или чтения. Ссылка, которую я вам дал, в значительной степени объясняет это, но если говорить кратко: когдаGC выполняет некоторую параллельную работу, все операции чтения и записи в кучу (пока выполняется сборщик мусора) "перехватываются" и применяются позже; так что параллельная фаза GC может закончить свою работу.

За final полей экземпляра, так как они не могут быть изменены (если отражения), эти барьеры могут быть опущены. И это не просто теория.

Shenandoah GCимеет их на практике (правда, ненадолго ), и вы можете, например:

-XX:+UnlockExperimentalVMOptions  
-XX:+UseShenandoahGC  
-XX:+ShenandoahOptimizeInstanceFinals

И в алгоритме GC будут оптимизации , которые сделают его немного быстрее. Это потому, что не будет никаких преград final, поскольку никто никогда не должен их модифицировать. Даже через отражение или JNI.

Евгений
источник
0

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

Сиджин
источник
0

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

Сантош Кумар
источник
0

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

  • Я должен сделать их окончательными, чтобы ими можно было поделиться с некоторым анонимным классом (например: создание потока демона и предоставление ему доступа к некоторому значению из включающего метода)

  • Я хочу сделать их окончательными (например: какое-то значение, которое не должно / не должно быть переопределено по ошибке)

Помогают ли они в быстрой сборке мусора?
AFAIK объект становится кандидатом на сборку мусора, если у него нет сильных ссылок на него, и в этом случае также нет гарантии, что они будут немедленно собраны мусором. В общем, считается, что сильная ссылка умирает, когда она выходит за пределы области видимости или пользователь явно переназначает ее на нулевую ссылку, таким образом, объявление их окончательной означает, что ссылка будет продолжать существовать до тех пор, пока существует метод (если только ее область явно не сужена до конкретный внутренний блок {}), потому что вы не можете переназначить конечные переменные (т.е. не можете переназначить значение null). Поэтому я думаю, что в отношении «final» сборки мусора может возникнуть нежелательная возможная задержка, поэтому нужно быть немного осторожным при определении области действия в качестве элементов управления, когда они станут кандидатами на сборку мусора .

жертвовать
источник