I think GC ensures that when the JVM tries to allocate an object on the heap and the heap is full, GC will remove all unreferenced objects to make room and the JVM will try again. It might fail again, giving you an out of memory error. So you might not see GC happen until the heap is very nearly full.
Because a GC cycle at full heap can be relatively slow, GC also runs at other times, perhaps in "background" mode, trying to do many small jobs instead of one huge one. So you might see GC run at any time.
Almost every new release of
Java has more efficient GC algorithms. They are wonderfully complicated. Google for java garbage collection if you're deeply curious.