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.

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

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.

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();

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

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

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.