The behavior that you observed is completely normal.
First, when
Java obtains memory from the OS, it never releases it back, even if the objects in the heap are released. It just holds on to the memory and recycles it for the next object being allocated on the heap. This is done because obtaining memory from the OS is expensive. So, Java holds on to the memory and reuses it
Second, GC is a very lazy operation. GC will not collect objects until it absolutely has to, or an internal timer triggers it. This is because GC is costly, and GC benifits from economy of scale. The more objects to collect, the faster it is to collect individual objects. So, the expected behavior on an application that is mostly idle is that heap usage grows till an internal timer triggers and cleans the heap. If the application is active and using lot of memory, GC will be triggered when heap usage hits max. If you watch memory usage on a tool like JConsole, you will see a graph that looks like a saw. This is completely normal.
What you want to look for is the bottom of the saw stays straight (or atleast goes down once processing is complete). That's your "real" memory usage