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

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

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

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

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

Это правда?


person Goran Martinic    schedule 20.11.2008    source источник
comment
на самом деле здесь есть две вещи. 1) когда вы пишете в метод локальное поле и экземпляр. Когда вы пишете в экземпляр, может быть полезным.   -  person Eugene    schedule 18.12.2019


Ответы (15)


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

public class MyClass {

   public final MyOtherObject obj;

}

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

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

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

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

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

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

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

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

person benjismith    schedule 20.11.2008
comment
Пища для размышлений. Я всегда думал, что встроенный код быстрее, но если jvm не хватает памяти, он тоже будет медленнее. хммммм ... - person WolfmanDragon; 10.12.2008
comment
Я просто предполагаю здесь ... но я предполагаю, что JIT-компилятор может встроить конечные примитивные значения (не объекты) для умеренного увеличения производительности. С другой стороны, встраивание кода способно произвести значительную оптимизацию, но не имеет ничего общего с конечными переменными. - person benjismith; 10.12.2008
comment
Невозможно присвоить null уже созданному конечному объекту, тогда, возможно, final вместо help может усложнить задачу - person Hernán Eche; 15.12.2011
comment
Можно также использовать {} для ограничения области видимости в большом методе, а не разбивать ее на несколько частных методов, которые могут не иметь отношения к другим методам класса. - person mmm; 10.12.2012
comment
Вы также можете использовать локальные переменные вместо полей, когда это возможно, чтобы улучшить сборку мусора памяти и уменьшить ссылочные связи. - person sivi; 10.03.2014
comment
это может снизить рабочую нагрузку. как многие говорили, переменные local final действительно бесполезны для сборщика мусора; но экземпляры нет. - person Eugene; 18.12.2019

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

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

person Aaron    schedule 20.11.2008

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

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

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

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

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

person SCdF    schedule 20.11.2008

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

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

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

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

person Kirk    schedule 19.06.2011
comment
В java есть выделение в стеке (примитивов и ссылок на объекты в куче): stackoverflow.com/a/8061692/32453 Java требует, чтобы объекты в том же закрытии, что и анонимный класс / лямбда, также были окончательными, но оказывается, что это просто для уменьшения требуемой памяти / кадра / уменьшения путаницы, поэтому это не связано с его сбором ... - person rogerdpack; 03.11.2017

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

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

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

person benjismith    schedule 20.11.2008
comment
Ты прав. Мне нужен был только быстрый пример, чтобы объяснить мой вопрос. - person Goran Martinic; 21.11.2008

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

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

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

final boolean debug = false;

......

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

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

person Thorbjørn Ravn Andersen    schedule 30.08.2009
comment
@Eugene Зависит от вашего менеджера безопасности и от того, встроил ли компилятор переменную или нет. - person Thorbjørn Ravn Andersen; 18.12.2019
comment
да, я просто был педантичен; больше ничего; также тоже предоставил ответ - person Eugene; 18.12.2019

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

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

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

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

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

Статьи:

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

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

person David Rodríguez - dribeas    schedule 20.11.2008

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

person dongilmore    schedule 20.11.2008

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

person Tom Hawtin - tackline    schedule 25.11.2008

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

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

person Matt Quigley    schedule 17.04.2013
comment
Это может быть правдой, но компилятор все еще может использовать окончательную информацию во время анализа потока данных. - person ; 18.07.2014
comment
@WernerVanBelle компилятор уже знает, что переменная устанавливается только один раз. Он уже должен провести анализ потока данных, чтобы знать, что переменная может иметь значение NULL, не инициализирована перед использованием и т. Д. Таким образом, локальные финалы не предлагают компилятору никакой новой информации. - person Matt Quigley; 20.07.2014
comment
Это не так. Анализ потока данных может вывести множество вещей, но можно иметь полную программу Тьюринга внутри блока, которая будет или не будет устанавливать локальную переменную. Компилятор не может заранее знать, будет ли переменная записана и будет ли она вообще постоянной. Поэтому компилятор без ключевого слова final не может гарантировать, что переменная является окончательной или нет. - person ; 21.07.2014
comment
@WernerVanBelle Я искренне заинтригован, вы можете привести пример? Я не понимаю, как может быть окончательное присвоение не конечной переменной, о которой компилятор не знает. Компилятор знает, что если у вас есть неинициализированная переменная, он не позволит вам ее использовать. Если вы попытаетесь назначить конечную переменную внутри цикла, компилятор вам не позволит. Каков пример, когда переменная МОЖЕТ быть объявлена ​​окончательной, но НЕ является окончательной, и компилятор не может гарантировать, что она является окончательной? Я подозреваю, что любой пример будет переменной, которую нельзя вообще объявить окончательной. - person Matt Quigley; 22.07.2014
comment
А, перечитав ваш комментарий, я вижу, что ваш пример - это переменная, инициализированная внутри условного блока. Эти переменные не могут быть окончательными, если только все пути условий не инициализируют переменную один раз. Итак, компилятор знает о таких объявлениях - вот как он позволяет вам скомпилировать конечную переменную, инициализированную в двух разных местах (подумайте final int x; if (cond) x=1; else x=2;). Таким образом, компилятор без ключевого слова final может гарантировать, является ли переменная окончательной или нет. - person Matt Quigley; 22.07.2014
comment
Я пытался сказать, что без final компилятор не знает, что переменная на самом деле является константой (хотя она может быть константой). Что касается финала, то в этом нет сомнений. Это означает, что final допускает больше оптимизаций, чем без него. - person ; 22.07.2014
comment
Хм, вам нужно привести пример. Я по-прежнему уверен, что компилятор знает с ключевым словом или без него, является ли переменная постоянной. Представьте себе: компилятор делает вид, что каждая локальная переменная объявлена ​​окончательной. Если он может скомпилироваться с последней переменной, теперь он знает, что он может быть final без ключевого слова. Если он не может скомпилироваться, он, наоборот, знает, что переменная не может быть окончательной. Добавил ли кто-то ключевое слово в текст, это не имеет отношения. - person Matt Quigley; 23.07.2014
comment
Случай, когда переменная постоянна, но компилятор не может ее обнаружить. int a = 1; в то время как (a ‹100) a * = 2; [в этой позиции всегда 128]. Тем не менее, я понимаю вашу проблему здесь, потому что этот код не может быть написан с помощью final. Интересным примером может быть случай, когда вы потенциально можете добавить final и когда это действительно поможет компилятору. - person ; 23.07.2014
comment
Пример "полного Тьюринга": {int a = 3; если (complexFalse ()) a = 4; } где complexFalse () - метод, который компилятор не может доказать, всегда будет ложным (см. проблему остановки). В этом случае вы не можете объявить финал без преобразования оператора if в тернарное выражение - и в этот момент поток данных все равно снова работает. IOW, хотя это и тривиально верно, на практике исключение бесполезно. - person Recurse; 10.12.2014
comment
@Recurse Однако этот пример неверен; a в вашем примере не может быть объявлен окончательным. {final int a = 3; if (complexFalse()) a = 4} не будет компилироваться в Java, поэтому включение или отсутствие ключевого слова final не применяется. Дело в том, что человек никогда не сможет сделать с модификатором final (в Java) ничего, что сообщило бы компилятору то, что он еще не знает. - person Matt Quigley; 11.12.2014

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

person Debasish Pakhira    schedule 07.02.2012

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

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

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

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

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

person Eugene    schedule 18.12.2019

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

person Sijin    schedule 20.11.2008

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

person santhosh kumar    schedule 27.04.2012

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

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

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

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

person sactiw    schedule 16.09.2014