Улучшает ли сборка мусора использование final для переменных в Java? - PullRequest
82 голосов
/ 21 ноября 2008

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

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

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

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

Это правда?

Ответы [ 14 ]

81 голосов
/ 21 ноября 2008

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

public class MyClass {

   public final MyOtherObject obj;

}

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

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

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

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

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

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

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

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

37 голосов
/ 21 ноября 2008

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

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

25 голосов
/ 21 ноября 2008

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

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

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

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

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

15 голосов
/ 19 июня 2011

Некоторые моменты, которые нужно прояснить:

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

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

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

11 голосов
/ 21 ноября 2008

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

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

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

5 голосов
/ 30 августа 2009

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

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

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

final boolean debug = false;

......

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

Печать не будет включена в байт-код.

3 голосов
/ 25 ноября 2008

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

3 голосов
/ 21 ноября 2008

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

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

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

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

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

Статьи:

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

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

3 голосов
/ 21 ноября 2008

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

2 голосов
/ 17 апреля 2013

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

Ответ на ваш вопрос - решительное нет.

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