Root Cause Analysis and Reproduction of MyBatis‑Induced OutOfMemoryError in Java Services
The article investigates frequent OutOfMemoryError incidents in a distributed Java service, explains heap and metaspace causes, analyzes MyBatis source that retains large SQL strings in memory, reproduces the issue with heavy IN clauses and limited heap, and offers practical mitigation advice.
Preface
After a recent CPU alarm, the service started throwing frequent OutOfMemoryError (OOM) messages, causing the entire distributed system to become unavailable. The operations team quickly restarted the service to restore business continuity, and the investigation was handed over to the author.
Reasons for OutOfMemoryError
OOM typically stems from two sources: insufficient heap space, where objects with strong references cannot be reclaimed and exceed the -Xmx limit; and metaspace exhaustion, introduced in Java 8 to replace the permanent generation, which resides outside the heap but can still overflow when class metadata grows.
Common Heap OOM Scenarios
Loading excessively large result sets from a database into memory.
Infinite loops that keep large objects referenced.
Failure to release resources such as connection pools or I/O streams.
Static collections that retain references indefinitely.
These are typical cases, though real‑world problems can be more obscure.
Phenomenon Analysis
Production logs show that MyBatis is the component triggering the memory overflow. MyBatis builds SQL statements using internal collection objects; when the generated SQL becomes very large, the collection grows dramatically and cannot be garbage‑collected, leading to OOM.
Because the Docker container lacks diagnostic tools like jstack or jmap and no heap dump was saved, direct thread‑level analysis was impossible.
Online research revealed that MyBatis stores placeholders and parameter objects in a Map (the ContextMap inside DynamicContext ). Under heavy concurrent queries with large parameter lists, these entries remain in memory, causing the heap to fill up.
MyBatis Source Code Analysis
The DynamicContext class contains a ContextMap called bindings . Methods like ForEachSqlNode.getBindings() put SQL fragments and parameters into this map. Since the map holds strong references, the large SQL strings cannot be reclaimed, especially under high concurrency, which eventually triggers OOM.
Scenario Reproduction
To reproduce the issue, the author crafted a test that concatenates a massive IN clause, spawns 50 threads to execute it, and runs the JVM with -Xmx256m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError . The console logs show frequent Full GC cycles until the process crashes with OOM.
Conclusion
The root cause is the unbounded growth of SQL strings held in MyBatis’s internal map. The remedy is to optimize SQL generation, avoid overly large IN clauses, and ensure that temporary collections are cleared promptly. Careful coding and SQL design are essential to prevent such hidden memory leaks.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
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.