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.
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
ConcurrentHashMapwhere 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
MonitorKeyobjects lingering in the heap because the
MonitorKeyclass lacks proper
equalsand
hashCodeoverrides.
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
visitmethod. 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
putIfAbsentto atomically insert a new
MonitorValueif 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
putIfAbsentmethod.
Ultimately, the incident highlights three key lessons: always override
equalsand
hashCodefor objects used as map keys, prefer atomic map operations over manual synchronization, and conduct thorough code reviews even for high‑profile architects.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.