2015-02-11 3 views
1

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

подход один:

for (Item item : items) { 
    MyObject myObject = new MyObject(); 
    //use myObject. 
} 

подход Два:

MyObject myObject = null; 
for (Item item : items) { 
    myObject = new MyObject(); 
    //use myObject. 
} 

Я понимаю: «Сводя к минимуму объем локальных переменных, вы увеличиваете читаемость и ремонтопригодность кода и уменьшить вероятность ошибка". (Джошуа Блох).

Но как насчет производительности/потребления памяти? В объектах Java объекты собираются Мусором, когда нет ссылки на объект. Если есть, например, 100000 объектов, тогда будет создано 100000 объектов. В подходе 1 каждый объект будет иметь ссылку (myObject), чтобы они не имели права на GC?

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

Или это компромисс между производительностью и удобочитаемостью кода & ремонтопригодность?

Что я неправильно понял?

Примечание: Предполагая, что я забочусь о производительности, и myObject не нужен после цикла.

Заранее спасибо

+0

Производительность может быть проблемой, но гораздо лучше написать правильный код и поместить объявление переменной внутри цикла. * Затем ** измерять ** производительность. * Никогда «не оптимизируйте» все, пока не убедитесь, что это необходимо. Все остальное - безумие. – markspace

+0

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

+0

В обеих версиях создано 100000 объектов. Единственное различие между ними заключается в том, что последний созданный объект не подходит для GC, как только в одном из них. – EJP

ответ

3

Если есть, например, 100000 объектов, тогда в подходе 1 будет создано 100000 объектов, и каждый объект будет иметь ссылку (myObject), чтобы они не имели права на GC?

Нет, от мусора. Точка зрения коллекционера обеими подходами работают одинаково, т.е. нет утечки памяти. С наступлением два, как только следующий оператор запускает

myObject = new MyObject(); 

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

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


ли GC знать, когда ссылки выходят из области видимости во время выполнения цикла, или он может только знать, в конце метода?

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

Обратите внимание, что я сказал, что объекты-сироты имеют право на gc, но не на то, что они собираются немедленно. Сбор мусора никогда не происходит в режиме реального времени, это происходит поэтапно. В знак фаза, все объекты не достижимые через в прямом эфире темы больше не нужно маркировать. Затем в фазе развертки память восстанавливается и дополнительно уплотняется так же, как дефрагментация жесткого диска. Таким образом, он работает скорее как партия, а не по частям.

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

Но все это не означает, что GC не может срабатывать, пока метод выполняется или даже во время работы цикла. Если у вас был, скажем, Message Message Processor, который обрабатывал 10 000 сообщений каждые 10 минут или около того, а затем спал между i.e, bean ждет в цикле, выполняет 10 000 итераций, а затем снова ждет; GC, безусловно, начнет действовать, чтобы вернуть память, хотя метод еще не завершился.

+0

Хорошие точки и согласны, но когда первый объект, созданный в подходе, стал подходящим? после цикла или после первой итерации? – webDeveloper

+0

На второй итерации, как только MyObject myObject = new MyObject(); 'будет выполнен,' MyObject', созданный в ** первой ** итерации, станет подходящим для GC. На этом фронте нет разницы между этими двумя подходами. –

+0

Знает ли GC, когда ссылки выходят из области действия во время выполнения цикла или может знать только в конце метода? EJP ниже прокомментировал, что он не знает во время цикла? Я не нашел ничего полезного в Google. Спасибо – webDeveloper

2

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

  • Когда единственная ссылка на них выходит за рамки (подход 1).
  • Когда единственной ссылкой на них присваивается другое значение (подход 2).

Итак, экземпляр MyObject будет иметь право на GC в конце каждой итерации цикла в зависимости от того, какой подход был использован. Разница (теоретически) между двумя подходами заключается в том, что JVM должен будет выделять память для новой ссылки на объекты каждой итерации в подходе 1, но не в подходе 2. Однако это предполагает компилятор Java и/или компилятор Just-In-Time не умны, чтобы оптимизировать подход 1 на самом деле действовать как подход 2.

в любом случае, я хотел бы пойти на более читаемым и менее подвержен ошибкам подхода 1 на том основании, что:

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

Хороший ответ, прямо к точке +1 – webDeveloper

0

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

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

можно найти в разделе 2.6 здесь: http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html

Это согласуется с другими языками, такими как C, где изменение размеров кадра стека как функция/метод выполняет это накладные расходы без видимой возврата.

Поэтому, где бы вы ни заявляли, это не должно иметь значения.

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

void foo() { 
    int x=6; 
    int y=7; 
    int z=8; 

    //..... 
} 

Versus

void bar() { 
    { 
    int x=6; 
    //.... 
    } 
    { 
    int y=7; 
    //.... 
    } 
    { 
    int z=8; 
    //.... 
    } 
} 

Обратите внимание, что bar() явно требуется только одну локальную переменную не 3.

Несмотря на то, что меньший размер кадра стека вряд ли окажет какое-либо влияние на производительность!

Однако, когда ссылка выходит за пределы области видимости, может сделать объект, на который он ссылается, доступный для сбора мусора. В противном случае вам нужно было бы установить ссылки на null, что является неопрятной и ненужной проблемой (и накладными расходами tinsy weenie).

Без вопросов вы должны объявлять переменные внутри цикла, если (и только если) вам не нужно обращаться к ним за пределами цикла.

Заблокированные заявления IMHO (например, bar) были использованы.

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

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

У меня есть коренастый алгоритм (Hashlife), где создание более ранних артефактов, доступных для сбора мусора во время метода, может сделать разницу между получением до конца и получением OutOfMemoryError.

+0

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

+0

@ BarrySW19 Согласен. Но иногда это не минимальные общие данные. Мой 'bar()' является примером игрушек. Предположим, что каждый раздел создает ряд вкладов в последний блок. Это уловка (и накладные расходы) в Java, чтобы вернуть не более одного значения из метода. Если вы прокомментируете разделы и имеете «складной редактор», вы начнете понимать, что декомпозиция во множество методов далека от единственного и всегда лучшего способа структурирования кода. Методы для повторного использования. Блоки для структуры! Я знаю, что это ересь. – Persixty

0

В обоих подходах объекты получат сбор мусора.

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

В подходе 2: Как и когда новая новая ссылка присваивается переменной myObject, ранее не имеет надлежащей ссылки. Так что раньше собирайте мусор и так далее, пока цикл не будет запущен.

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

+0

Не совсем, я бы подумал, что GC достаточно умен, чтобы собирать объекты, которые выходят за рамки. См. Ответы Майка, Рави и Барри. – webDeveloper

+0

@webDeveloper GC не * знает * когда * ссылки * выходят за рамки, за исключением конца метода. Нет инструкции по байт-коду, соответствующей внутреннему '}', * ergo *, не может быть никаких связанных действий JVM или HotSpot или GC. – EJP

+0

@EJP Я ошибаюсь, говоря, что в первом приближении первый объект, созданный на итерации, будет иметь право на GC во второй итерации и будет поднят GC, если он должен был работать во время второй итерации. Я считаю, что это то, что говорят Рави и БарриСв19? – webDeveloper

Смежные вопросы