Вопрос о сборщике мусора Java, нулях и утечке памяти

Предположим, я реализую очередь в java, и у меня есть ссылка на начальный узел с именем ini и еще одна ссылка на последний узел с именем last. Теперь я начинаю вставлять объекты в очередь. В какой-то момент я решаю, что мне нужна операция по очистке очереди. Затем я делаю это:

ini = null;
last = null;

У меня утечка памяти? Узлы между ini и last все еще связаны цепочкой и все еще имеют свои данные, я думаю, но в то же время есть сборщик мусора.

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


person Community    schedule 20.12.2008    source источник


Ответы (5)


Пока ни один элемент в очереди не упоминается где-либо еще в вашем коде, сборщик мусора сможет освободить эту память. Установка указателей на null в Java отличается от установки в C, где установка указателя malloc на null предотвращает его освобождение. В Java память освобождается, когда она становится недоступной. В Java нет утечек памяти (в смысле C/C++), если вы не используете собственный код через JNI.

Упрощенный сборщик мусора будет просто подсчитывать количество ссылок на объект и освобождать этот объект, когда счетчик ссылок достигнет нуля, но он не сможет работать с циклами ссылок (A -> B, A -> B -> C -> А и др.). Алгоритмы Java GC выполняют тест на живучесть, где они строят эталонный граф всех объектов в системе. GC выполняет обход графа, и любые узлы (объекты), которые недоступны, помечаются как неиспользуемые и доступные для перераспределения. Корни графа (начальные точки обхода) включают переменные в стеках потоков, статические переменные и ссылки, хранящиеся в машинном коде через JNI. Подробнее см. здесь: http://java.sun.com/developer/Books/performance/performance2/appendixa.pdf

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

public class Stack {
  private final Object[] stack = new Object[10];
  private int top = 0;
  public void push(Object obj) {stack[top++] = obj;}
  public Object pop() {return stack[top--]; }
}

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

public Object pop() {Object ret = stack[top]; stack[top--] = null; return ret;}
person sk.    schedule 20.12.2008
comment
Ключевым моментом является отсутствие других указателей на узлы. Если какой-либо узел доступен, все они будут доступны (при условии, что следующий и предыдущий указатели в списке), поэтому ни один из них не будет очищен. - person Clayton; 21.12.2008
comment
Клейтон, правда? это означало бы, что они недоступны, но они все равно не будут удалены сборщиком мусора. я не знаю, но это звучит странно. - person Johannes Schaub - litb; 21.12.2008
comment
не больше похоже на то, что если последняя прямая или косвенная ссылка стека на объект потеряна, то объект освобождается? - person Johannes Schaub - litb; 21.12.2008
comment
litb: если у вас есть указатель на один из элементов в списке, и все элементы списка объединены в цепочку с указателями следующего/предыдущего, установка нулевого значения для начала и конца списка не делает их недоступными, поскольку вы d все еще есть ссылка в середине списка. - person Clayton; 21.12.2008
comment
litb: Я думаю, что эта проблема заключается в том, почему, когда вы реализуете связанный список, вы не связываете фактические элементы в списке вместе. Вы используете специальный объект ListItem для связи и позволяете ему указывать на объект, который вы там храните. Пока вы не сохраняете указатель на объект ListItem, все в порядке. - person Clayton; 21.12.2008
comment
litb: чтобы быть более ясным: если у вас есть цепочка объектов, которая недоступна из другого места, она будет очищена. Эти объекты могут указывать друг на друга, но сборщик мусора обнаружит, что в цепочку не входят живые указатели, поэтому он будет знать, что все они недоступны. - person Clayton; 21.12.2008
comment
Клейтон, я только что спросил в ##java, и они сказали, что я прав. просто проверяя доступность, виртуальная машина определяет, считает ли она их мусором или нет. поэтому после установки для головы и хвоста значения null они будут считаться мусором. - person Johannes Schaub - litb; 21.12.2008
comment
litb: Вы правы: если группа объектов недоступна извне группы (т. е. это остров), то она очищается. Моя точка зрения заключалась в том, что если в списке есть другой указатель, отличный от головы или хвоста, то недостаточно очистить только голову и хвост. - person Clayton; 21.12.2008
comment
о верно. тогда я с вами соглашусь :) извините, я пропустил ваш комментарий, где вы пишете, чтобы было ясно: это было как раз перед моим другим комментарием :) - person Johannes Schaub - litb; 21.12.2008
comment
Без проблем. Мы все равно примерно об одном и том же говорили. Маловероятно, что то, что я описал, произойдет, но это возможно. - person Clayton; 21.12.2008

Это будет работать нормально. Сборщик мусора обнаружит, что узлы недоступны, поэтому все они будут очищены.

person Clayton    schedule 20.12.2008

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

Вы можете увидеть, как реализован метод clear() в java.util.LinkedList.

public void clear() {
    Entry<E> e = header.next;
    while (e != header) {
        Entry<E> next = e.next;
        e.next = e.previous = null;
        e.element = null;
        e = next;
    }
    header.next = header.previous = header;
    size = 0;
    modCount++;
}

http://tech.puredanger.com/2009/02/11/linkedblockingqueue-garbagecollection/ затрагивает проблему.

person grayger    schedule 27.02.2009

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

person Peter Lawrey    schedule 27.02.2009

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

import java.lang.ref.*;

public class MemoryLeak1 {

    MyListItem leakedItem = null;
    WeakReference[] refs = null;

    public static void main(String[] args) {
        WeakReference ref = null;
        MyListItem item = null;
        MemoryLeak1 leak = new MemoryLeak1();
        int i;

        leak.doit(); // create a memory leak
        System.gc(); // force the gc to run;

        // At this point the list has been explicitly cleared,
        // has gone out of scope, and the GC has run.
        // However, leak.leakedItem is still holding a
        // reference to an item in the list, so anything
        // reachable from that item is still alive.

        // show what's still around...
        for (i = 0; i < 10; i++) {
            ref = leak.refs[i];
            item = (MyListItem)ref.get();
            if (item == null) { System.out.println("" + i + " = null"); }
            else { System.out.println("" + i + " = " + (String)item.thing); }
        }
        System.out.println("---------------------");

        // now let's free some additional items...
        for (i = 1; i <= 3; i++) {
            item = leak.leakedItem;
            leak.leakedItem = item.next;
            leak.leakedItem.prev = null;
            item.prev = null;
            item.next = null;
        }
        item = null;

        System.gc(); // force the gc to run again

        // this time we should get fewer items
        for (i = 0; i < 10; i++) {
            ref = leak.refs[i];
            item = (MyListItem)ref.get();
            if (item == null) { System.out.println("" + i + " = null"); }
            else { System.out.println("" + i + " = " + (String)item.thing); }
        }
        System.out.println("---------------------");

        // now clear the last reference
        leak.leakedItem = null;

        System.gc(); // force the gc to run again

        // this time we should none
        for (i = 0; i < 10; i++) {
            ref = leak.refs[i];
            item = (MyListItem)ref.get();
            if (item == null) { System.out.println("" + i + " = null"); }
            else { System.out.println("" + i + " = " + (String)item.thing); }
        }
        System.out.println("---------------------");
    }

    public void doit() {
        this.refs = new WeakReference[10];
        MyList list = new MyList();
        MyListItem item = null;

        // add strings to the list.
        // set each into the array of soft refs 
        // set a ptr to the 6th item in an instance variable
        for (int i = 0; i < 10; i++) {
            item = new MyListItem();
            item.thing = new String("string" + i);
            list.insert(item);
            if (i == 5) this.leakedItem = item;
            this.refs[i] = new WeakReference(item);
        }

        // clear the list, but don't clear the
        // additional ptr to the 6th item
        list.clear();
    }
}

class MyList {
    MyListItem head = null;
    MyListItem tail = null;

    void clear() {
        head = null;
        tail = null;
    }

    void insert(MyListItem item) {
        if (head == null) {
            // empty list
            item.next = null;
            item.prev = null;
            tail = item;
            head = item;
        }
        else if (head == tail) {
            // one item in list
            item.next = head;
            item.prev = null;
            tail = head;
            head = item;
        }
        else {
            // multiple items in list
            item.next = head;
            item.prev = null;
            head = item;
        }
    }

    MyListItem remove() {
        MyListItem item = tail;
        if (item != null) {
            tail = item.prev;
            if (tail == null) {
                head = null;
            }
            else {
                tail.next = null;
            }
            item.next = null;
            item.prev = null;
        }
        return item;
    }
}

class MyListItem {
    MyListItem next = null;
    MyListItem prev = null;
    Object thing = null;
}
person Clayton    schedule 21.12.2008
comment
System.gc() не запускает сборщик мусора. - person Bombe; 21.12.2008
comment
Бомба: Хорошо, возможно, слово force не совсем подходящее, но вот из документа API: Вызов метода gc предполагает, что [JVM] затрачивает усилия на переработку неиспользуемых объектов... Когда управление возвращается из вызова метода, [JVM ] приложил все усилия, чтобы освободить место. - person Clayton; 21.12.2008
comment
Бобме: попробуйте этот пример кода с вызовами gc() и без них, и вы увидите разницу. Возможно, все, что делает gc(), — это обновляет мой массив WeakReferences, но что-то он делает. - person Clayton; 21.12.2008