Сегодня мы с коллегами обсуждаем использование final
ключевого слова в Java для улучшения сборки мусора.
Например, если вы напишете такой метод:
public Double doCalc(final Double value)
{
final Double maxWeight = 1000.0;
final Double totalWeight = maxWeight * value;
return totalWeight;
}
Объявление переменных в методе final
поможет сборщику мусора очистить память от неиспользуемых переменных в методе после выхода из метода.
Это правда?
java
garbage-collection
final
Горан Мартинич
источник
источник
Ответы:
Вот немного другой пример, с конечными полями ссылочного типа, а не с конечными локальными переменными типа значения:
public class MyClass { public final MyOtherObject obj; }
Каждый раз, когда вы создаете экземпляр MyClass, вы будете создавать исходящую ссылку на экземпляр MyOtherObject, и GC должен будет перейти по этой ссылке для поиска живых объектов.
JVM использует алгоритм GC mark-sweep, который должен проверять все активные ссылки в «корневых» местоположениях GC (как и все объекты в текущем стеке вызовов). Каждый живой объект «помечается» как живой, и любой объект, на который ссылается живой объект, также помечается как живой.
После завершения фазы отметки сборщик мусора просматривает кучу, освобождая память для всех немаркированных объектов (и уплотняя память для оставшихся живых объектов).
Также важно понимать, что память кучи Java разделена на «молодое поколение» и «старое поколение». Все объекты изначально выделяются в молодом поколении (иногда его называют «ясли»). Поскольку большинство объектов недолговечны, сборщик мусора более агрессивно очищает недавний мусор от молодого поколения. Если объект переживает цикл сбора молодого поколения, он перемещается в старое поколение (иногда называемое «постоянным поколением»), которое обрабатывается реже.
Итак, мысленно я скажу: «Нет,« последний »модификатор не помогает сборщику мусора снизить его рабочую нагрузку».
На мой взгляд, лучшая стратегия оптимизации управления памятью в Java - как можно быстрее исключить ложные ссылки. Вы можете сделать это, присвоив объектной ссылке «null», как только вы закончите ее использовать.
Или, что еще лучше, минимизируйте размер каждой области объявления. Например, если вы объявляете объект в начале метода из 1000 строк, и если объект остается живым до закрытия области действия этого метода (последняя закрывающая фигурная скобка), то объект может оставаться в живых намного дольше, чем на самом деле необходимо.
Если вы используете небольшие методы, всего с дюжиной или около того строк кода, то объекты, объявленные в этом методе, будут быстрее выпадать из области видимости, и сборщик мусора сможет выполнять большую часть своей работы в гораздо более эффективных молодое поколение. Вы не хотите, чтобы объекты передавались старшему поколению без крайней необходимости.
источник
Объявление локальной переменной
final
не повлияет на сборку мусора, это только означает, что вы не можете изменить переменную. Приведенный выше пример не должен компилироваться, поскольку вы изменяетеtotalWeight
отмеченную переменнуюfinal
. С другой стороны, объявление примитива (double
вместоDouble
)final
позволит встроить эту переменную в вызывающий код, что может вызвать некоторое улучшение памяти и производительности. Это используется, когдаpublic static final Strings
в классе есть несколько человек.В общем, компилятор и среда выполнения оптимизируют там, где это возможно. Лучше написать код соответствующим образом и не пытаться быть слишком сложным. Используйте,
final
если вы не хотите изменять переменную. Предположим, что любые простые оптимизации будут выполняться компилятором, и если вы беспокоитесь о производительности или использовании памяти, используйте профилировщик для определения реальной проблемы.источник
Нет, это категорически не так.
Помните, что
final
это не означает постоянство, это просто означает, что вы не можете изменить ссылку.final MyObject o = new MyObject(); o.setValue("foo"); // Works just fine o = new MyObject(); // Doesn't work.
Может быть некоторая небольшая оптимизация, основанная на знании того, что JVM никогда не придется изменять ссылку (например, не проверять, изменилась ли она), но она будет настолько незначительной, чтобы не о чем беспокоиться.
Final
следует рассматривать как полезные метаданные для разработчика, а не как оптимизацию компилятора.источник
Некоторые моменты, которые необходимо прояснить:
Удаление ссылки не должно помочь GC. Если бы это было так, это означало бы, что ваши переменные выходят за рамки диапазона. Единственным исключением является случай объектного кумовства.
В Java пока нет распределения внутри стека.
Объявление переменной final означает, что вы не можете (при нормальных условиях) присвоить этой переменной новое значение. Поскольку final ничего не говорит о области видимости, он ничего не говорит о ее влиянии на сборщик мусора.
источник
Что ж, я не знаю об использовании модификатора final в этом случае или его влиянии на сборщик мусора.
Но я могу сказать вам следующее: использование вами значений в штучной упаковке, а не примитивов (например, Double вместо double) будет размещать эти объекты в куче, а не в стеке, и будет производить ненужный мусор, который GC придется очистить.
Я использую упакованные примитивы только тогда, когда это требуется для существующего API, или когда мне нужны примитивы, допускающие значение NULL.
источник
Конечные переменные не могут быть изменены после начального присвоения (выполняется компилятором).
Это не меняет поведения сборки мусора как такового. Единственное, что эти переменные не могут быть обнулены, когда они больше не используются (что может помочь при сборке мусора в ситуациях с ограниченным объемом памяти).
Вы должны знать, что final позволяет компилятору делать предположения о том, что следует оптимизировать. Встраивание кода, но не включая код, который заведомо недостижим.
final boolean debug = false; ...... if (debug) { System.out.println("DEBUG INFO!"); }
Println не будет включен в байтовый код.
источник
Есть не очень известный случай с поколенными сборщиками мусора. (Краткое описание читайте в ответе Бенджисмита. для более глубокого понимания читайте статьи в конце).
Идея GC поколений состоит в том, что в большинстве случаев необходимо учитывать только молодые поколения. Корневое местоположение проверяется на наличие ссылок, а затем проверяются объекты молодого поколения. Во время этого более частого сканирования ни один объект старого поколения не проверяется.
Проблема заключается в том, что объекту не разрешается иметь ссылки на более молодые объекты. Когда долгоживущий объект (старого поколения) получает ссылку на новый объект, эта ссылка должна явно отслеживаться сборщиком мусора (см. Статью IBM о сборщике JVM горячей точки ), что фактически влияет на производительность сборки мусора .
Причина, по которой старый объект не может ссылаться на более молодой, заключается в том, что, поскольку старый объект не проверяется в второстепенных коллекциях, если единственная ссылка на объект сохраняется в старом объекте, он не будет отмечен и будет ошибочно освобожден на этапе очистки.
Конечно, как указывали многие, ключевое слово final не влияет на сборщик мусора, но оно гарантирует, что ссылка никогда не будет изменена на более молодой объект, если этот объект переживет второстепенные коллекции и перейдет в более старую кучу.
Статьи:
IBM о сборке мусора: история , JVM в горячей точке и производительность . Возможно, они уже не полностью действительны, поскольку датируются 2003/04 годом, но они дают легкую для чтения информацию о сборщиках мусора.
Солнце о настройке сборки мусора
источник
GC работает с недоступными ссылками. Это не имеет ничего общего с «финалом», который является просто утверждением одноразового задания. Возможно ли, что некоторые виртуальные машины GC могут использовать "final"? Я не понимаю, как и почему.
источник
final
для локальных переменных и параметров не имеет значения для создаваемых файлов классов, поэтому не может влиять на производительность во время выполнения. Если у класса нет подклассов, HotSpot в любом случае рассматривает этот класс как окончательный (он может отменить позже, если загружен класс, который нарушает это предположение). Я считаю, чтоfinal
методы во многом похожи на классы.final
в статическом поле может позволить интерпретировать переменную как «постоянную времени компиляции», и на этой основе javac будет выполнять оптимизацию.final
on fields дает JVM некоторую свободу игнорировать отношения " происходит до" .источник
Кажется, есть много ответов, которые являются блуждающими предположениями. По правде говоря, для локальных переменных на уровне байт-кода нет модификатора final. Виртуальная машина никогда не узнает, были ли ваши локальные переменные определены как final или нет.
Ответ на ваш вопрос категорический нет.
источник
final int x; if (cond) x=1; else x=2;
). Таким образом, компилятор без ключевого слова final может гарантировать, является ли переменная окончательной или нет.Все методы и переменные могут быть переопределены по умолчанию в подклассах. Если мы хотим сохранить подклассы от переопределения членов суперкласса, мы можем объявить их как final с помощью ключевого слова final. Например,
final int a=10;
final void display(){......}
создание метода final гарантирует, что функциональность, определенная в суперклассе, никогда не будет изменена. Точно так же нельзя изменить значение конечной переменной. Конечные переменные ведут себя как переменные класса.источник
Строго говоря о полях экземпляра ,
final
может немного улучшить производительность, если конкретный сборщик мусора захочет это использовать. Когда происходит одновременное выполнениеGC
(это означает, что ваше приложение все еще работает, пока выполняется сборщик мусора ), см. Это для более широкого объяснения , сборщики мусора должны использовать определенные барьеры при выполнении записи и / или чтения. Ссылка, которую я вам дал, в значительной степени объясняет это, но если говорить кратко: когдаGC
выполняет некоторую параллельную работу, все операции чтения и записи в кучу (пока выполняется сборщик мусора) "перехватываются" и применяются позже; так что параллельная фаза GC может закончить свою работу.За
final
полей экземпляра, так как они не могут быть изменены (если отражения), эти барьеры могут быть опущены. И это не просто теория.Shenandoah GC
имеет их на практике (правда, ненадолго ), и вы можете, например:И в алгоритме GC будут оптимизации , которые сделают его немного быстрее. Это потому, что не будет никаких преград
final
, поскольку никто никогда не должен их модифицировать. Даже через отражение или JNI.источник
Единственное, что я могу придумать, - это то, что компилятор может оптимизировать конечные переменные и встроить их как константы в код, в результате чего вы не получите выделенной памяти.
источник
Абсолютно точно, пока срок жизни объекта становится короче, что дает большое преимущество управления памятью, недавно мы исследовали функциональность экспорта с переменными экземпляра в одном тесте и в другом тесте с локальной переменной уровня метода. во время нагрузочного тестирования JVM выдает ошибку outofmemoryerror при первом тесте, и JVM останавливается. но во втором тесте успешно удалось получить отчет из-за лучшего управления памятью.
источник
Единственный раз, когда я предпочитаю объявлять локальные переменные окончательными, это когда:
Я должен сделать их окончательными, чтобы ими можно было поделиться с некоторым анонимным классом (например: создание потока демона и предоставление ему доступа к некоторому значению из включающего метода)
Я хочу сделать их окончательными (например: какое-то значение, которое не должно / не должно быть переопределено по ошибке)
источник