views:

879

answers:

5

Suppose I'm implementing a queue in java and I have a reference to the initial node, called ini and another to the last one, called last. Now, I start inserting objects into the queue. At one point, I decide I want an operation to clear the queue. Then I do this:

ini = null;
last = null;

Am I leaking memory? The nodes between ini and last are all chained still and still have their data, I guess, but at the same time there's the garbage collector.

An alternative would be to access each element and then null their references to the next node, but then I'd be basically doing it like in C++, except I wouldn't be explicitly using delete.

+17  A: 

As long as no item in the queue is referenced anywhere else in your code, the garbage collector will be able to reclaim that memory. Setting pointers to null in Java is not the same as in C where setting a malloc'ed pointer to null prevents it from being freed. In Java, memory is reclaimed when it is no longer reachable. There are no memory leaks in Java (in the C/C++ sense), as long as you're not using native code via JNI.

A simplistic garbage collector would just count the number of references to an object and deallocate that object when the reference count reached zero, but that wouldn't be able to deal with reference cycles (A -> B, A -> B -> C -> A, etc.). The Java GC algorithms do a liveness test, where they construct a reference graph of all objects in the system. The GC does a graph traversal and any nodes (objects) that are not reachable are marked as unused and available for reallocation. The roots of the graph (starting ponts of the traversal) include variables on thread stacks, static variables, and references held by native code via JNI. See more here: http://java.sun.com/developer/Books/performance/performance2/appendixa.pdf

It is still possible to have reference leaks. This refers to situations where you're holding on to a reference to an object longer than is needed. For example:

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--]; }
}

Ignoring the overflow/underflow possibility, after you call Stack.pop(), the array member variable still has a reference to the object that was returned. It will prevent that object from being garbage collected until the surrounding Stack instance is no longer referenced. This is one of the rare instances where it is necessary to set a variable to null so that its memory can be reclaimed:

public Object pop() {Object ret = stack[top]; stack[top--] = null; return ret;}
sk
The lack of other ptrs to the nodes is key. If any node is reachable, they'll all be reachable (assuming next and prev ptrs in the list), so none will get cleaned up.
Clayton
clayton, really? that would mean they are not reachable, but they would still not be garbage collected. i don't know but that sounds weird.
Johannes Schaub - litb
isn't it more like if the last direct or indirect stack reference to the object is lost, then the object is free'ed?
Johannes Schaub - litb
litb: If you have a ptr to one of the items in the list, and the list items are all chained together with next/prev ptrs, setting the head and tail of the list to null doesn't make them unreachable, since you'd still have a link into the "middle" of the list.
Clayton
litb: I think this issue is why when you implement a linked list you don't link the actual items in the list together. You use a special "ListItem" object for the "linkage", and let it point to the object you're storing there. As long as you don't keep a ptr to the ListItem object you're fine.
Clayton
litb: to be more clear: if you have a chain of objects that's not reachable from anywhere else, that will be cleaned up. Those objects can point to each other, but the GC will detect that there are no "live" ptrs coming into the chain, so it will know that they're all unreachable.
Clayton
Clayton, i just asked in ##java and they said i'm right. by merely checking the reachability, the VM determines whether it considers them as garbage or not. so after setting head and tail to null, they will be considered garbage.
Johannes Schaub - litb
litb: You are correct: If a group of objects is not reachable from outside the group (i.e., it is an "island"), then it is cleaned up. My point was that if there is another pointer into the list other than the head or tail, then it's not enough to clear only the head and tail.
Clayton
oh right. then i'm going to agree with you :) im sorry i missed your comment where you write "to be clear:" that came just before my other comment :)
Johannes Schaub - litb
No problem. We were pretty much saying the same thing anyway. It's not likely that what I described would happen, but it is possible.
Clayton
+3  A: 

That will work fine. The GC will detect that the nodes are not reachable, so they'll all get cleaned up.

Clayton
A: 

Here's some code to demonstrate how a stray handle into the middle of a list structure can keep the GC from cleaning up completely:

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;
}
Clayton
System.gc() does not force the garbage collector to run.
Bombe
Bombe: Ok, "force" may not be the right word, but here's from the api doc: "Calling the gc method suggests that the [JVM] expend effort toward recycling unused objects .... When control returns from the method call, the [JVM] has made a best effort to reclaim space.
Clayton
Bobme: try that sample code with and without the calls to gc(), and you'll see that there's a difference. Maybe all gc() is doing is updating my array of WeakReferences, but it's doing something.
Clayton
+3  A: 

Yes, GC works in that case. But elements between head and tail may survive and then enter old generation space and then they will be collected during full GC. As you know, full GC is expensive. As far as performance is concerned, nulling them is better.

You can see how clear() method of java.util.LinkedList is implemented.

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/ touches the issue.

grayger
A: 

If you suspect you have a memory leak I suggest you use a memory profiler to see how objects are being retained over time. A fast memory leak will be obvious with such a tool so if you create a test for something you suspect leaks and repeat it many times you will be able to see the leak and why the objects are being retained.

Peter Lawrey