DEV Community

Cover image for Memory Optimization in Java – Or, How to Stop Leaking Your Sanity
Rus Kuzmin
Rus Kuzmin

Posted on

Memory Optimization in Java – Or, How to Stop Leaking Your Sanity

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 Listor Mapis 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 Stringobjects 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:

  1. Look at your object lifecycles.
  2. Watch your collections.
  3. 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)

Collapse
 
nevodavid profile image
Nevo David

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?

Collapse
 
goldennoodles profile image
Rus Kuzmin • Edited

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.

Collapse
 
aichayasmine_06060b6f73c6 profile image
AichaYasmine

Interesting read, thanks for sharing.

Collapse
 
goldennoodles profile image
Rus Kuzmin

Glad you enjoyed!

Collapse
 
dotallio profile image
Dotallio

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?

Collapse
 
goldennoodles profile image
Rus Kuzmin

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)