Чи присвоєння об'єктів нулю в Java впливає на збір сміття?


85

Чи присвоєння посилання на невикористаний об’єкт nullу Java покращує процес збору сміття якимось вимірюваним способом?

Мій досвід роботи з Java (і C #) навчив мене, що часто протиставляється інтуїтивно зрозумілим спробам перехитрити віртуальну машину або компілятор JIT, але я бачив, як співробітники використовують цей метод, і мені цікаво, чи це хороша практика для вибору або одне з тих забобонів програмування вуду?

Відповіді:


66

Як правило, ні.

Але як і всі речі: це залежить. GC в Java в наш час ДУЖЕ хороший, і все повинно бути очищено дуже скоро після того, як він вже недоступний. Це відбувається лише після того, як залишено метод для локальних змінних, і коли екземпляр класу більше не посилається на поля.

Вам потрібно явно вказати нуль, лише якщо ви знаєте, що в іншому випадку на нього залишатимуться посилання. Наприклад, масив, який зберігається навколо. Можливо, ви захочете обнулити окремі елементи масиву, коли вони більше не потрібні.

Наприклад, цей код із ArrayList:

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
         System.arraycopy(elementData, index+1, elementData, index,
             numMoved);
    elementData[--size] = null; // Let gc do its work

    return oldValue;
}

Крім того, явне обнулення об'єкта не призведе до того, що об'єкт буде зібраний раніше, ніж якщо він просто вийшов за межі природної дії, доки не залишиться посилань.

Обидва:

void foo() {
   Object o = new Object();
   /// do stuff with o
}

і:

void foo() {
   Object o = new Object();
   /// do stuff with o
   o = null;
}

Функціонально еквівалентні.


7
Ви також можете захотіти нульові посилання всередині методів, які можуть використовувати великий обсяг пам'яті або тривати довго для виконання, або в коді в програмі Swing, яка може працювати тривалий час. Коротко кажучи, методи або недовговічні об'єкти, не варто зайвого заплутаного коду.
Джон Гарднер,

1
Тож чи правильно, коли ми обнуляємо об'єкт, Смітник збирає цей об'єкт негайно ??
Мухаммед Бабар

1
Ні. Збірник сміття періодично виконується, щоб перевірити, чи були об’єкти, на які не вказують посилальні змінні. Коли ви встановлюєте для них значення null, а потім викликаєте, System.gc()він буде видалений (за умови, що немає інших посилань, що вказують на цей об'єкт).
JavaTechnical

2
@JavaTechnical Наскільки мені відомо, не гарантується, що за допомогою виклику System.gc () розпочнеться збір сміття.
Джек,

12

З мого досвіду, частіше за все люди нулюють посилання з параної, не з необхідності. Ось короткий настанова:

  1. Якщо об’єкт A посилається на об’єкт B і вам більше не потрібно це посилання, а об’єкт A не підходить для збору сміття, тоді вам слід явно обнулити поле. Немає необхідності нулювати поле, якщо обгороджувальний об'єкт все одно збирає сміття. Обнулення полів у методі dispose () майже завжди марно.

  2. Немає необхідності обнулювати посилання на об'єкти, створені в методі. Вони будуть очищені автоматично, як тільки метод завершиться. Виняток з цього правила полягає в тому, що якщо ви використовуєте дуже довгий метод або якийсь масивний цикл, і вам потрібно переконатися, що деякі посилання очищаються до кінця методу. Знову ж таки, ці випадки надзвичайно рідкісні.

Я б сказав, що переважну більшість часу вам не потрібно буде нулювати посилання. Намагатися перехитрити збирач сміття марно. Ви просто отримаєте неефективний, нечитабельний код.


11

Хороша стаття - це сьогоднішній жах кодування .

Принцип роботи GC полягає у пошуку об'єктів, на які немає жодних покажчиків, область пошуку - це купа / стек та будь-які інші пробіли, які вони мають. Отже, якщо ви встановите для змінної значення null, фактичний об'єкт тепер не вказується ніким, а отже, може бути GC'd.

Але оскільки GC може не працювати в той самий момент, ви насправді не купуєте собі нічого. Але якщо ваш метод досить довгий (з точки зору часу виконання), можливо, він того вартий, оскільки ви збільшите свої шанси GC зібрати цей об'єкт.

Проблема також може ускладнитися оптимізацією коду, якщо ви ніколи не використовуєте змінну після того, як встановили для неї значення null, було б безпечною оптимізацією видалити рядок, що встановлює значення як null (на одну інструкцію менше для виконання). Тож ви, можливо, не отримуєте жодного покращення.

Отже, підсумовуючи, так, це може допомогти, але це не буде детерміновано .



Хороша ідея щодо безпечної оптимізації, яка видаляє рядок, встановлюючи значення ref для нуля.
asgs

8

Принаймні в Java, це зовсім не програмування вуду. Коли ви створюєте об'єкт у Java, використовуючи щось на зразок

Foo bar = new Foo();

ви робите дві речі: по-перше, ви створюєте посилання на об'єкт, а по-друге, ви створюєте сам об'єкт Foo. Поки існує те чи інше посилання, конкретний об'єкт не може бути gc'd. однак, коли ви присвоюєте цьому посиланню значення null ...

bar = null ;

і припускаючи, що ніщо інше не посилається на об'єкт, він звільнений і доступний для gc наступного разу, коли збирач сміття пройде повз нього.


12
Але вихід із сфери дії зробить те ж саме без зайвого рядка коду. Якщо це метод локальний, немає потреби. Після того, як ви залишите метод, об’єкт буде придатним для GC.
duffymo

2
Добре. тепер для поп-вікторини побудуйте приклад коду, для якого цього НЕ достатньо. Підказка: вони існують.
Charlie Martin

7

Це залежить.

Взагалі кажучи, коротше ви зберігаєте посилання на свої об'єкти, швидше вони збираються.

Якщо для виконання вашого методу потрібно приблизно 2 секунди, і після однієї секунди виконання методу вам більше не потрібен об’єкт, має сенс очистити будь-які посилання на нього. Якщо GC побачить, що через одну секунду на ваш об’єкт все ще посилаються, наступного разу він може перевірити це за хвилину чи близько того.

У будь-якому випадку, встановлення всіх посилань на null за замовчуванням є для мене передчасною оптимізацією, і ніхто не повинен це робити, якщо це не відбувається в окремих рідкісних випадках, коли це помітно зменшує споживання пам'яті.


6

Явне встановлення посилання на null замість того, щоб просто дозволити змінній вийти за межі області дії, не допомагає збиральнику сміття, якщо об’єкт, який утримується, є дуже великим.

Як правило, встановлення посилань на null, означає для READER коду, з яким цей об’єкт повністю закінчений, і його більше не слід турбувати.

Подібного ефекту можна досягти, ввівши вужчий обсяг, вставивши додатковий набір брекетів

{
  int l;
  {  // <- here
    String bigThing = ....;
    l = bigThing.length();
  }  // <- and here
}

це дозволяє bigThing збирати сміття відразу після виходу з вкладених фігурних дужок.


Хороша альтернатива. Це дозволяє уникати явного встановлення для змінної значення null та попередження про залишки, які деякі IDE відображають про те, що нульове призначення не використовується.
jk7

5
public class JavaMemory {
    private final int dataSize = (int) (Runtime.getRuntime().maxMemory() * 0.6);

    public void f() {
        {
            byte[] data = new byte[dataSize];
            //data = null;
        }

        byte[] data2 = new byte[dataSize];
    }

    public static void main(String[] args) {

        JavaMemory jmp = new JavaMemory();
        jmp.f();

    }

}

Над програмою кидає OutOfMemoryError. Якщо ви коментуєте data = null;, OutOfMemoryErrorце вирішено. Завжди є гарною практикою встановити для невикористовуваної змінної значення null


Тобто, imo, твердий набіг у світ солом’яних аргументів людини. Те, що ви МОЖЕТЕ створити ситуацію, коли встановлення змінної значення null вирішить проблему в цьому конкретному випадку (і я не впевнений, що ви це зробили), не означає, що встановлення значення null - це гарна ідея в кожному випадку. Додавання коду для встановлення для кожної змінної, яку ви більше не використовуєте, дорівнює нулю, навіть тоді, коли незабаром після цього вони виходять за рамки просто додає до роздуття коду і робить код менш ремонтопридатним.
RHSeeger

Чому сміття масиву байтів [] не збирається на момент, коли дані змінної виходять за межі обсягу?
Grav

2
@Grav: вихід із зони дії не гарантує, що збирач сміття збиратиме локальні об’єкти. Однак встановлення значення null запобігає виключенню OutOfMemory, оскільки ви гарантовано запускаєте GC, перш ніж вдаватися до викиду виключення OutOfMemory, і якщо для об’єкта встановлено значення NULL, його можна збирати.
Стефанос Т.


4

Одного разу я працював над додатком для відеоконференцій і помітив величезну величезну різницю в продуктивності, коли я витратив час на нульові посилання, як тільки об’єкт мені більше не потрібен. Це було в 2003-2004 роках, і я лише уявляю, що з того часу GC став ще розумнішим. У моєму випадку у мене щосекунди виходили і виходили із зони дії сотні об’єктів, тому я помітив GC, коли він періодично запускався. Однак після того, як я вказав на нульові об'єкти, GC припинив призупиняти мою програму.

Тож це залежить від того, що ви робите ...


2

Так.

З "Прагматичного програміста" с.292:

Встановивши посилання на NULL, ви зменшуєте кількість покажчиків на об'єкт на один ... (що дозволить збиральнику сміття видалити його)


Думаю, це застосовне лише тоді, коли підрахунок посилань algo. використовується чи інакше також?
Nrj

3
Ця порада застаріла для будь-якого сучасного збирача сміття; і підрахунок посилань GC має значні проблеми, такі як кругові посилання.
Лоуренс Дол,

Це також було б вірно в позначенні та розгортці GC; ймовірно, у всіх інших. Той факт, що у вас є одне посилання менше, не означає, що воно враховується. Відповідь tweakt пояснює це. Краще зменшити область дії, ніж встановити значення NULL.

1

Я припускаю, що OP стосується таких речей:

private void Blah()
{
    MyObj a;
    MyObj b;

    try {
        a = new MyObj();
        b = new MyObj;

        // do real work
    } finally {
        a = null;
        b = null;
    }
}

У цьому випадку, чи не буде Віртуальна Марка позначити їх як GC, як тільки вони все одно залишать область дії?

Або, з іншої точки зору, чи явне встановлення елементів для нуля призведе до того, що вони отримуватимуть GC'd раніше, ніж, якщо вони просто вийдуть за межі обсягу? Якщо це так, віртуальна машина може витратити час на обробку об'єкта, коли пам'ять у будь-якому випадку не потрібна, що насправді призведе до погіршення продуктивності використання процесора, оскільки це буде раніше.


Час, в який працює GC, є недетермінованим. Я не вірю, що встановлення об’єкта взагалі nullвпливає на поведінку GC.
харто

0

" Це залежить "

Я не знаю про Java, але в .net (C #, VB.net ...) зазвичай не потрібно призначати нуль, коли вам більше не потрібен об'єкт.

Однак зверніть увагу, що це "зазвичай не потрібно".

Аналізуючи ваш код, компілятор .net робить хорошу оцінку часу життя змінної ..., щоб точно визначити, коли об'єкт більше не використовується. Отже, якщо ви пишете obj = null, це може насправді виглядати так, ніби obj все ще використовується ... у цьому випадку привласнення null є контрпродуктивним.

Є кілька випадків, коли насправді може допомогти присвоєння нуля. Один із прикладів - ви маєте величезний код, який працює тривалий час, або метод, який працює в іншому потоці, або якийсь цикл. У таких випадках може допомогти присвоєння null, щоб GC легко знав, що він більше не використовується.

Для цього не існує жорсткого правила. Пройшовши вищезазначене місце, присвоює нуль у вашому коді, і запустити профайлер, щоб перевірити, чи це допомагає якимось чином. Швидше за все, ви не бачите вигоди.

Якщо це .net-код, який ви намагаєтеся оптимізувати, то мій досвід полягав у тому, що ретельна обережність із методами Dispose та Finalize насправді є вигіднішою, ніж турбота про нулі.

Деякі посилання на тему:

http://blogs.msdn.com/csharpfaq/archive/2004/03/26/97229.aspx

http://weblogs.asp.net/pwilson/archive/2004/02/20/77422.aspx


0

Навіть якби анулювання посилання було незначно більш ефективним, чи варто було б потворність того, що потрібно переписувати свій код цими потворними ануляціями? Вони б лише метушню і затемнювали код намірів, який їх містить.

Це рідкісна кодова база, яка не має кращого кандидата на оптимізацію, ніж спроба перехитрити збирач сміття (рідше все-таки є розробники, яким вдається перехитрити його). Швидше за все, ваші зусилля краще витратити в іншому місці, відкинувши цей хитрий парсер Xml або знайшовши якусь можливість кешувати обчислення. Ці оптимізації буде простіше визначити кількістю і не вимагатимуть забруднення кодової бази шумом.


0

При подальшому виконанні вашої програми значення деяких членів даних будуть використовуватися для обробки вихідних даних, видимих ​​зовні для програми. Інші можуть використовуватися чи не використовуватися, залежно від майбутніх (і неможливо передбачити) вхідних даних програми. Інші члени даних можуть бути гарантовано не використані. Усі ресурси, включаючи пам’ять, виділені тим невикористаним даним, марно витрачаються. Робота збирача сміття (GC) полягає в тому, щоб усунути даремно витрачену пам’ять. Для GC було б згубно усунути те, що було потрібно, тому використовуваний алгоритм міг бути консервативним, зберігаючи більше ніж суворий мінімум. Він може використовувати евристичну оптимізацію, щоб покращити свою швидкість, за рахунок збереження деяких предметів, які насправді не потрібні. Існує багато потенційних алгоритмів, які може використовувати GC. Тому можливо, що ви внесете зміни до своєї програми, і які не впливають на коректність вашої програми, тим не менше можуть вплинути на роботу GC, або змусити його швидше виконувати ту саму роботу, або швидше виявити невикористані елементи. Отже, такого роду зміни, встановлення посилання на об’єкт, що не додаєтьсяnull, в теорії не завжди вуду.

Це вуду? Як повідомляється, є частини коду бібліотеки Java, які роблять це. Автори цього коду набагато кращі за пересічних програмістів і або знають, або співпрацюють з ними, програмістами, які знають деталі реалізацій збирача сміття. Так що дозволяє припустити , що це іноді користь.


0

Як ви вже сказали, є оптимізації, тобто JVM знає місце, коли востаннє використовувалася змінна, і об'єкт, на який вона посилається, може бути GCed відразу після цієї останньої точки (все ще виконується в поточній області). Тож обнулення посилань у більшості випадків не допомагає GC.

Але може бути корисно уникнути проблеми "кумівства" (або "плаваючого сміття") ( докладніше читайте тут або дивіться відео ). Проблема існує, оскільки купа розділена на старе і молоде покоління, і застосовуються різні механізми ГХ: малий ГХ (який швидко і часто трапляється для очищення молодого покоління) та основний Гк (що спричиняє довшу паузу для очищення старого покоління). "Непотизм" не дозволяє збирати сміття молодого покоління, якщо на нього посилається сміття, яке вже було призначене старому поколінню.

Це є "патологічним", оскільки БУДЬ-який просунутий вузол призведе до просування ВСІХ наступних вузлів, доки GC не вирішить проблему.

Щоб уникнути непотизму, непогано обнуляти посилання на об’єкт, який передбачається видалити. Ви можете побачити цю техніку застосовується в класах JDK: LinkedList і LinkedHashMap

private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    // ...
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.