Backend Development 6 min read

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.

Architecture Digest
Architecture Digest
Architecture Digest
Root Cause Analysis and Reproduction of MyBatis‑Induced OutOfMemoryError in Java Services

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.

backendJavaperformanceMyBatisOutOfMemoryError
Architecture Digest
Written by

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.

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.