If you really really you care about memory footprint above all else, don't use G1. It's a complex GC with auxiliary data structures and it avoids doing full GCs by only collecting the old gen piece-meal. It also has a pause time goal by default that causes it to prefer growing the heap rather than missing its pause time goal.
Stick to serial gc or the throughput parallel collector.
You can try the following though: -XX:MaxHeapFreeRatio=30 -XX:MinHeapFreeRatio=20 -XX:InitiatingHeapOccupancyPercent=30
, that should tell G1 to more aggressively yield back memory to the OS and to start collecting the old gen sooner.