Analyzing and Reproducing OutOfMemoryError in MyBatis-based Java Services
This article examines the causes of Java OutOfMemoryError in a distributed backend service, analyzes MyBatis-related memory leaks, demonstrates a reproducible scenario with large SQL concatenations and multithreading, and offers practical mitigation strategies to prevent heap and metaspace overflow.
Preface
After a previous CPU alarm, the service soon encountered frequent OutOfMemoryError (OOM) in production logs, rendering the service unusable and causing widespread failures in Skywalking traces; the team restarted the service to restore business continuity while the root cause investigation began.
Reasons for OutOfMemoryError
OutOfMemoryError generally stems from two sources: insufficient heap space and insufficient metaspace.
Heap space shortage occurs when objects remain strongly referenced and cannot be garbage‑collected, eventually exceeding the -Xmx maximum heap size.
Metaspace, introduced in Java 8 to replace the permanent generation, resides off‑heap; it stores class metadata via pointers and can also overflow if not properly sized.
Common Heap Memory Overflow Scenarios
Loading excessively large query results into memory.
Infinite loops that keep large objects referenced.
Failure to manually release resources such as connection pools or I/O streams.
Static collections that retain object references indefinitely.
These are typical cases, though unusual issues can also cause OOM.
Phenomenon Analysis
Production logs show a MyBatis‑related memory overflow; MyBatis builds SQL strings using collection classes, and when SQL becomes large, the collections grow dramatically and cannot be reclaimed, leading to OOM.
Because the Docker container lacks tools like jstack or jmap and no heap dump was saved, detailed thread‑level analysis was impossible.
Online research eventually provided insight into the problem.
The referenced article describes how MyBatis stores generated SQL and parameters in a Map; with many parameters and concurrent threads, the Map retains large objects, causing high memory usage and OOM.
MyBatis Source Code Analysis
Inspecting DynamicContext reveals a ContextMap (extends HashMap ) that holds bindings. ForEachSqlNode puts SQL parameters and placeholders into this map, preventing garbage collection under heavy concurrent queries, which leads to OOM.
Scenario Reproduction
To reproduce the issue, the SQL IN clause was enlarged, 50 threads were launched, and the JVM was started with -Xmx256m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError .
The console logs show frequent Full GC cycles, ultimately leading to OOM.
Conclusion
After identifying the root cause, the remedy is to optimize SQL generation—avoid excessively large concatenated statements, limit parameter counts, and ensure proper resource cleanup—to prevent heap and metaspace exhaustion in Java backend services.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
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.