I am not an expert on this, but I believe there are multiple ways the gc can be implemented, possibly even within a JVM and which is used determined at runtime.
but basically, the way I understand it is that there is a list of objects that exist. There is a list of references. You could go through the list of references, and mark each object to which it points. When you're done, you clean up any object that has not been marked.
It's a little more complicated, because you could have a references IN objects. If a.ref points to b, and b.ref points to a, but nothing points to either a or b, the objects are unreachable, but still have references to them. The JVM can figure that out and gc them.
You should also be aware that there is no guarantee that gc will run during your programs execution. even calling System.gc() [or whatever the method is] won't FORCE it to run.
You are only guaranteed that gc will run before you get an out-of-memory error.