As I know static fields (along with Threads, local variables and method arguments, JNI references) act as GC roots.
I cannot provide a link that would confirm this, but I have read a lot of articles on it.
Why can't a non-static field act as a GC root?
First off, we need to be sure we're on the same page as to what a tracing garbage collection algorithm does in its mark phase.
At any given moment, a tracing GC has a number of objects that are known to be alive, in the sense that they are reachable by the running program as it stands right now. The main step of mark phrase involves following the non-static fields of those objects to find more objects, and those new objects will now also be known to be alive. This step is repeated recursively until no new alive objects are found by traversing the existing live objects. All objects in memory not proved live are considered dead. (The GC then moves to the next phase, which is called the sweep phase. We don't care about that phase for this answer.)
Now this alone is not enough to execute the algorithm. In the beginning, the algorithm has no objects that it knows to be alive, so it can't start following anyone's non-static fields. We need to specify a set of objects that are considered known to be alive from the start. We choose those objects axiomatically, in the sense that they don't come from a previous step of the algorithm -- they come from outside. Specifically, they come from the semantics of the language. Those objects are called roots.
In a language like Java, there are two sets of objects that are definite GC roots. Anything that is accessible by a local variable that's still in scope is obviously reachable (within its method, which still hasn't returned), therefore it's alive, therefore it's a root. Anything that is accessible through a static field of a class is also obviously reachable (from anywhere), therefore it's alive, therefore it's a root.
But if non-static fields were considered roots as well, what would happen?
Say you instantiate an
ArrayList<E>
. Inside, that object has a non-static field that points to anObject[]
(the backing array that represents the storage of the list). At some point, a GC cycle starts. In the mark phase, theObject[]
is marked as alive because it is pointed to by theArrayList<E>
private non-static field. TheArrayList<E>
is not pointed to by anything, so it fails to be considered alive. Thus, in this cycle, theArrayList<E>
is destroyed while the backingObject[]
survives. Of course, at the next cycle, theObject[]
also dies, because it is not reachable by any root. But why do this in two cycles? If theArrayList<E>
was dead in the first cycle and ifObject[]
is used only by a dead object, shouldn't theObject[]
also be considered dead in the same move, to save time and space?That's the point here. If we want to be maximally efficient (in the context of a tracing GC), we need to get rid of as many dead objects as possible in a single GC.
To do that, a non-static field should keep an object alive only if the enclosing object (the object that contains the field) has been proved to be alive. By contrast, roots are objects we call alive axiomatically (without proof) in order to kick-start the algorithm's marking phase. It is in our best interest to limit the latter category to the bare minimum that doesn't break the running program.
For example, say you have this code:
Foo foo
. That's it, that's our only root.Foo
, which is marked as alive and then we attempt to find its non-static fields. We find one of them, theBar bar
field.Bar
, which is marked as alive and then we attempt to find its non-static fields. We find that it contains no more fields that are reference types, so the GC doesn't need to bother for that object anymore.Alternatively:
Integer a
is a root and theFoo this
reference (the implicit reference that all non-static methods get) is also a root. The local variableFoo foo
frommain
is also a root becausemain
hasn't gone out of scope yet.Integer
and instance ofFoo
(we find one of these objects twice, but this doesn't matter for the algorithm), which are marked as alive and then we attempt to follow their non-static fields. Let's say the instance ofInteger
has no more fields to class instances. The instance ofFoo
gives us oneBar
field.Bar
, which is marked as alive and then we attempt to find its non-static fields. We find that it contains no more fields that are reference types, so the GC doesn't need to bother for that object anymore.A non static field has a reference held by the instance that contains it, so it cannot be a GC root on its own right.