Backend Development 8 min read

When ‘No‑Comment’ Java Code Triggers a Memory Leak – A ConcurrentHashMap Study

A newly hired architect boasts high‑concurrency expertise but writes un‑commented Java code that misuses ConcurrentHashMap, leading to memory leaks; the ensuing investigation reveals missing equals/hashCode implementations, race conditions in a visit method, and a debate between synchronized blocks and putIfAbsent for safe updates.

macrozheng
macrozheng
macrozheng
When ‘No‑Comment’ Java Code Triggers a Memory Leak – A ConcurrentHashMap Study

A new architect with a BAT background joins the department, claiming massive high‑concurrency experience. He proposes the most concurrent task: aggregating average response time and total request count for all interfaces using an AOP‑based solution.

The implementation relies on a

ConcurrentHashMap

where each request retrieves a monitoring key, increments the associated value, and records timing data. The architect dismisses comments, citing influence from Netflix practices.

After deployment, the service suffers a memory overflow. Using Eclipse MAT and

jmap

, the team discovers millions of

MonitorKey

objects lingering in the heap because the

MonitorKey

class lacks proper

equals

and

hashCode

overrides.

The missing overrides cause distinct key objects that should be equal to occupy separate map entries, leading to unbounded growth and the memory leak.

Another issue appears in the

visit

method. Although it uses

ConcurrentHashMap

, the sequence of retrieving a key, checking for null, creating a new value, and putting it back is not atomic, causing race conditions where two threads may overwrite each other’s data.

<code>线程1:获取key为a的值
线程2:获取key为a的值
线程1:a为null,生成一个b
线程2:a为null,生成一个c
线程1:保存a=b
线程2:保存a=c
</code>

One proposed fix is to make the method

synchronized

, ensuring exclusive access. An alternative, more performant solution uses

putIfAbsent

to atomically insert a new

MonitorValue

if the key is absent, then safely increment counters.

<code>MonitorKey key = new MonitorKey(url, desc);
MonitorValue value = monitors.putIfAbsent(key, new MonitorValue());
value.count.getAndIncrement();
value.totalTime.getAndAdd(timeCost);
value.avgTime = value.totalTime.get() / value.count.get();
</code>

The technical director argues for the minimal‑change synchronized approach, citing production stability, while others advocate the lock‑free

putIfAbsent

method.

Ultimately, the incident highlights three key lessons: always override

equals

and

hashCode

for objects used as map keys, prefer atomic map operations over manual synchronization, and conduct thorough code reviews even for high‑profile architects.

Javabackend developmentmemory leakSynchronizationThread SafetyConcurrentHashMapHashCode
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.