Let's start with the obvious, Java is great. It’s got a garbage collector, it keeps your hands mostly clean of manual memory management, and it’ll let you write code that just works. Until it doesn’t... Until that sweet little app of yours starts slowing down like it’s stuck in mud and your grafana memory usage chart looks like it’s climbing Everest with no plans to come back.
So yes... Java handles memory for you. But if you don’t understand what it’s doing behind the scenes, it’s only a matter of time before your app becomes jabba the hutt.
Let’s talk about how not to let that happen.
The Garbage Collector Is Not Your Maid
Java's garbage collector is fantastic. It takes care of unused objects so you don’t have to manually free()
them like in C. But here’s the catch - the garbage collector only collects what it knows is garbage (shocker). If you're still holding references to unused objects even accidentally... It won’t touch them.
This is how memory leaks happen in Java.
Example?
- Caching too aggressively.
- Static fields that accumulate data over time.
- Listeners that never get removed.
- Sessions that never end.
Garbage Collection isn't magic. It's bookkeeping. If your bookkeeping is bad, your memory goes missing (I wish I came up with that).
Be Wary of Collections
The humble List
or Map
is often the first suspect when memory issues arise. Why you ask? Because we love shoving things in them and then forgetting about them.
Growing but not shrinking... Collections grow automatically, but they don’t shrink unless you ask them to.
Unbounded queues or caches? If you’re putting things in and never evicting them, guess what? That’s not a cache. That’s hoarding.
Tip: Use WeakHashMap
when possible for caches - or find some good libs online.
In a weak hashmap, items can be cleared by the garbage collector if nothing else is using the key
Object Creation Matters More Than You Think
Creating a lot of short lived objects isn’t always bad... modern JVMs are optimized for this. But if you’re doing it in tight loops or in performance critical paths, it can hurt.
Examples:
Boxing primitives
Integer &| Double
in hot paths? That adds up.Creating new
String
objects from substrings for no reason?... We all know that person.Logging full stack traces every 5 milliseconds? Please stop.
Know Your String Pitfalls
Ye Ol' String... Everyone’s favourite (probably the first data type you learnt when writing a HelloWorld app) but it’s also a memory hog if you’re not careful.
Every String
in Java is immutable. So str += "To Infinity And Beyond!"
inside a loop? That’s creating a whole new String each time (also your memory usage really will go to infinity and beyond... sorry ha!).
Some history for you (Something I learnt recently), old school Java used to store strings in the PermGen. Sure that's gone in newer versions BUT poorly managed strings still cause bloat.
Prior to Java 7, String literal in pool may be collected only if perm gen is included in collection (not every Full/Old GC includes perm gen in scope).
This one's for free: Use StringBuilder
or StringBuffer
when building strings in loops.
Tuning the JVM Isn’t Cheating
The JVM is powerful, but it doesn’t know your app. That’s your job.
Set appropriate
-Xms
and-Xmx
Heap size values.-
Choose the right garbage collector for your use case (G1, ZGC, etc).
Monitor using tools like
VisualVM
,jConsole
, or others to get visibility into what’s happening under the hood.Use the
-XX:+HeapDumpOnOutOfMemoryError
flag. When things go bad and you want to know why.
You Can’t Optimize What You Don’t Understand
You don’t need to be a JVM engineer to write memory efficient code... But you do need to know how memory works. Blind trust in the garbage collector will only get you so far - maybe pretty far (depending on what you're building) but when your memory usage is going to the moon? Goodluck, I hope you have lots of coffee and a nice boss.
So take the time:
- Look at your object lifecycles.
- Watch your collections.
- Avoid unnecessary allocations.
You'll eventually start doing these things subconsciously..
And yes... every now and then... read a heap dump. It builds character.
A few things I’d love to discuss:
- What memory profiling tool do you swear by?
- Ever had a memory leak in Java that gave you nightmares?
- Are modern GC algorithms good enough to let us “stop caring”?
Still no gin,
Rus
Top comments (6)
been there with those sneaky leaks and string messes, lol - feels like you only learn this the hard way tbh. you think there’s ever a point when it actually gets easy or is it always chasing down stuff?
My honest answer? No haha! It's still a massive pain for me to this day. The only thing I'd say get easier/better is the amount of effort and time required to find what's happened and where.
And you're spot on about learning it the hard way, it's not easy but you can get better the more you do it.
Interesting read, thanks for sharing.
Glad you enjoyed!
I still have flashbacks from tracking a leak in a custom cache that was 'just' a HashMap… Took hours with VisualVM to find it. Have you tried any profilers that made tracking these painless?
Ha! Don't we all! Like I mentioned in the article it's usually - always a collection xD
And re any other profilers - nothing really, it's one of those things that I don't think will ever really be painless (unless you plug in some sort of AI into your app that tracks, monitors and resolves - but think we're still a ways away from that)