As pcalcao mentioned, the JVM is telling you that it is spending too much time in garbage collection, and isn't doing enough real work, so it's just going to bail out.
This is kind of a safety valve to avoid the case that you haven't actually run out of memory, but are close enough that you really aren't making any forward progress. I won't belabor this point - this answer has a lot more detail, but I'll mention a couple things that might apply to your case:
This is more likely to happen if you SoftReference
s to cache things - as the heap grows towards the maximum size, these references get cleared and possibly repeatedly regenerated (depending on if your key loop touches) them, resulting in some bad behavior where GC occurs constantly but always recovers enough memory to keep going because it is able to clear some soft references - even the JDK suffers from this as they use this kind of caching in their Locale
classes.
You can disable this behaviour if you really want using -XX:-UseGCOverheadLimit. Still, the problem it indicates is real - you are spending less than 2% of your runtime doing real work.
Where are you printing out those memory values? Depending on how your program is structured, it may be generating a lot of garbage and using most of the heap in some inner loop, but at place you put your diagnositc output, the garbage as been reclaimed. -verbose:gc can give you a much better picture of actual GC behaviour.