There are many ways to implement GC, but most modern implementations use some variation of the "mark and sweep" algorithm. Basically, the collector starts from all static member variables and the stack frames of all threads. It "marks" every object to which it finds a reference. Then it looks at the member variables of all those objects, and marks the referenced objects. And then the members of those, etc. Eventually, all reachable objects are marked. Then the GC looks through memory for unmarked objects; those are the unreachable ones that can be collected.