views:

643

answers:

8

From my understanding, garbage collection in java cleans up some object iff nothing else is 'pointing' to that object. My question is, what happens if we have something like:

class Node{
    public object value;
    public Node next;
    public Node(object o, Node n) { value = 0; next = n;}
}

 //...some code
{
Node a = new Node("a", null), b = new Node("b", a), c = new Node("c", b);
a.next = c;
}//end of scope
//...other code

a, b, and c should be garbage collected, but they are all being referenced by other objects. How does the Java garbage collection deal with this? (or is it simply a memory drain?)

+7  A: 

Java's GC is smart enough to recognize circular references, so these objects will be collected. See the section on unreachable objects in Sun's The Truth About Garbage Collection for the gory details.

Bill the Lizard
I figured as much :). I'm more curious (intellectually) how exactly that happens. Does the GC generate some sort of closure from the objects?
AlexeyMK
Do you have a reference for that? It's hard to test it.
tangens
I added a reference. You can also override an object's finalize() method to find out when it gets collected (although that's about the only thing I'd recommend using finalize() for).
Bill the Lizard
Just to clarify that last comment... put a debug print statement in the finalize method that prints out a unique id for the object. You'll be able to see all the objects that reference each other get collected.
Bill the Lizard
A: 

Garbage collection doesn't usually mean "clean some object iff nothing else is 'pointing' to that object" (that's reference counting). Garbage collection roughly means finding objects that can't be reached from the program.

So in your example, after a,b, and c go out of scope, they can be collected by the GC, since you can't access these objects anymore.

Amnon
"Garbage collection roughly means finding objects that can't be reached from the program". In most GC algorithms it is actually the other way around. You start with the GC roots and see what you can find, the rest is considered unreferenced garbage.
Fredrik
Reference counting *is* one of the two main implementation strategies for garbage collection. (The other is tracing.)
Jörg W Mittag
@Jörg: Most of the time today, when people talk about garbage collectors they are referring to collectors based on some kind of mark'n'sweep algorithm. Ref counting is typically what you are stuck with if you don't have a garbage collector. It is true that ref counting is in a sense a garbage collection strategy but hardly no gc existing today that is built on top of it so saying that it is a gc strategy is just going to confuse people because in practice it is no longer a gc strategy but an alternative way to manage memory.
Fredrik
+2  A: 

This article goes into depth about the garbage collector (conceptually... there are several implementations). The relevant part to your post is "A.3.4 Unreachable".

TofuBeer
+2  A: 

The Java GCs don't actually behave as you describe. It's more accurate to say that they start from a base set of objects, frequently called "GC roots", and will collect any object that can not be reached from a root.
GC roots include things like:

  • static variables
  • local variables (including all applicable 'this' references) currently in the stack of a running thread

So, in your case, once the local variables a, b, and c go out of scope at the end of your method, there are no more GC roots that contain, directly or indirectly, a reference to any of your three nodes, and they'll be eligible for garbage collection.

TofuBeer's link has more detail if you want it.

Sbodd
A: 

Bill answered your question directly. As Amnon said, your definition of garbage collection is just reference counting. I just wanted to add that even very simple algorithms like mark and sweep and copy collection easily handle circular references. So, nothing magic about it!

Claudiu
+5  A: 

A garbage collector starts from some "root" set of places that are always considered "reachable", such as the CPU registers, stack, and global variables. It works by finding any pointers in those areas, and recursively finding everything they point at. Once it's found all that, everything else is garbage.

There are, of course, quite a few variations, mostly for the sake of speed. For example, most modern garbage collectors are "generational", meaning that they divide objects into generations, and as an object gets older, the garbage collector goes longer and longer between times that it tries to figure out whether that object is still valid or not -- it just starts to assume that if it has lived a long time, chances are pretty good that it'll continue to live even longer.

Nonetheless, the basic idea remains the same: it's all based on starting from some root set of things that it takes for granted could still be used, and then chasing all the pointers to find what else could be in use.

Interesting aside: may people are often surprised by the degree of similarity between this part of a garbage collector and code for marshaling objects for things like remote procedure calls. In each case, you're starting from some root set of objects, and chasing pointers to find all the other objects those refer to...

Jerry Coffin
What you are describing is a tracing collector. There are other kinds of collectors. Of particular interest for this discussion are reference counting collectors, which *do* tend to have trouble with cycles.
Jörg W Mittag
@Jörg W Mittag: Certainly true -- though I don't know of a (reasonably current) JVM that uses reference counting, so it seems unlikely (at least to me) that it makes much difference to the original question.
Jerry Coffin
The Jikes RVM has a reference counting garbage collector, for example. Although that particular algorithm (cleverly named *The Recycler*) *can* detect and collect cycles.
Jörg W Mittag
@Jörg W Mittag:At least by default I believe Jikes RVM currently uses the Immix collector, which is a region-based tracing collector (though it does also use reference counting). I'm not sure whether you're referring to that reference counting, or another collector that uses reference counting without tracing (I'd guess the latter, since I've never heard of Immix being calling "recycler").
Jerry Coffin
I got mixed up a bit: the Recycler is (was?) implemented in Jalapeno, the algorithm I was thinking about, which is (was?) implemented in Jikes is *Ulterior Reference Counting*. Atlhough, of course, saying that Jikes uses this or that garbage collector is quite futile, given that Jikes and especially MMtk are specifically designed to rapidly develop and test different garbage collectors within the same JVM.
Jörg W Mittag
Ulterior Reference Counting was designed in 2003 by the same people who designed Immix in 2007, so I guess that the latter probably superseded the former. URC was specifically designed so that it can be combined with other strategies, and in fact the URC paper explicitly mentions that URC is only a stepping stone towards a collector that combines the advantages of tracing and reference counting. I guess Immix is that collector. Anyway, the Recycler is a *pure* reference counting collector, which nonetheless can detect and collect cycles: http://WWW.Research.IBM.Com/people/d/dfb/recycler.html
Jörg W Mittag
+1  A: 

You are correct. The specific form of garbage collection you describe is called "reference counting". The way it works (conceptually, at least, most modern implementations of reference counting are actually implemented quite differently) in the simplest case, looks like this:

  • whenever a reference to an object is added (e.g. it is assigned to a variable or a field, passed to method, and so on), its reference count is increased by 1
  • whenever a reference to an object is removed (the method returns, the variable goes out of scope, the field is re-assigned to a different object or the object which contains the field gets itself garbage collected), the reference count is decreased by 1
  • as soon as the reference count hits 0, there is no more reference to the object, which means nobody can use it anymore, therefore it is garbage and can be collected

And this simple strategy has exactly the problem you decribe: if A references B and B references A, then both of their reference counts can never be less than 1, which means they will never get collected.

There are four ways to deal with this problem:

  1. Ignore it. If you have enough memory, your cycles are small and infrequent and your runtime is short, maybe you can get away with simply not collecting cycles. Think of a shell script interpreter: shell scripts typically only run for a few seconds and don't allocate much memory.
  2. Combine your reference counting garbage collector with another garbage collector which doesn't have problems with cycles. CPython does this, for example: the main garbage collector in CPython is a reference counting collector, but from time to time a tracing garbage collector is run to collect the cycles.
  3. Detect the cycles. Unfortunately, detecting cycles in a graph is a rather expensive operation. In particular, it requires pretty much the same overhead that a tracing collector would, so you could just as well use one of those.
  4. Don't implement the algorithm in the naive way you and I would: since the 1970s, there have been multiple quite interesting algorithms developed that combine cycle detection and reference counting in a single operation in a clever way that is significantly cheaper than either doing them both seperately or doing a tracing collector.

By the way, the other major way to implement a garbage collector (and I have already hinted at that a couple of times above), is tracing. A tracing collector is based on the concept of reachability. You start out with some root set that you know is always reachable (global constants, for example, or the Object class, the current lexical scope, the current stack frame) and from there you trace all objects that are reachable from the root set, then all objects that are reachable from the objects reachable from the root set and so on, until you have the transitive closure. Everything that is not in that closure is garbage.

Since a cycle is only reachable within itself, but not reachable from the root set, it will be collected.

Jörg W Mittag
A: 

I think nothing will be garbage collected here. Every memory is pointed to by something; a points to new Node("a", null), etc. Everything is reachable as long as no one is setting any of a,b or c to null. If nothing is done with those variables it will be memory drain, but garbage collector can not touch them.

fastcodejava
This is plain wrong, see other posts.
Blaisorblade