Why Spring Data Elasticsearch Triggers Full GC Alerts: Uncovering Class Metadata Leaks
A June 15 Full GC alert in a Spring Data Elasticsearch service led to a deep investigation using jstat, jmap, VisualVM, and OQL, revealing massive class loading from ES PO packages caused by per‑request ElasticsearchRestTemplate instances, which exhausted metaspace and triggered repeated garbage collections.
Origin
On June 15, an alert reported that the JVM performed two Full GC cycles on a pre‑release machine.
The affected application processes MQ messages and scheduled tasks, isolated from the online environment, and no traffic spike was observed.
Investigation
Initial suspicion was a monitoring glitch; CPU fluctuated but heap usage remained stable, pointing to non‑heap memory.
Using jstat we observed metaspace usage at 97.49%.
Next, a heap dump was taken with jmap and analyzed in VisualVM.
VisualVM showed 118,588 loaded classes, far exceeding normal expectations.
Class instance counts were examined, but the issue turned out to be excessive class loading rather than large objects.
OQL queries were run to count classes per package:
var packageClassSizeMap = {};</code><code>// 遍历统计以最后一个逗号做分割</code><code>heap.forEachClass(function (it) {</code><code> var packageName = it.name.substring(0, it.name.lastIndexOf('.'));</code><code> if (packageClassSizeMap[packageName] != null) {</code><code> packageClassSizeMap[packageName] = packageClassSizeMap[packageName] + 1;</code><code> } else {</code><code> packageClassSizeMap[packageName] = 1;</code><code> }</code><code>});</code><code>// 排序 因为Visual VM的查询有数量限制。</code><code>var sortPackageClassSizeMap = [];</code><code>map(sort(Object.keys(packageClassSizeMap), function (a, b) {</code><code> return packageClassSizeMap[b] - packageClassSizeMap[a];</code><code>}), function (it) {</code><code> sortPackageClassSizeMap.push({</code><code> package: it,</code><code> classSize: packageClassSizeMap[it]</code><code> })</code><code>});</code><code>sortPackageClassSizeMap;The query revealed that the package com.jd.bapp.match.sync.query.es.po contained 92,172 classes, although the source code defines fewer than 20.
Each ES PO object generated many classes ending with Instantiator_….
Some classes had instances, others did not.
Root Cause
The application creates a new ElasticsearchRestTemplate for every search request. This instantiation rebuilds the Spring Data Elasticsearch mapping context each time, causing the metadata region to load a fresh set of persistent entities.
Consequently, class metadata accumulates, leading to metaspace exhaustion and repeated Full GC events.
Post‑mortem Recommendations
Restart the service to clear the metadata region; optionally increase metaspace size as a temporary mitigation.
Avoid recreating ElasticsearchRestTemplate per request; reuse a singleton or share the mapping context.
Monitor class‑loading metrics and metaspace usage; set alerts before the region overflows.
Use OQL scripts (as shown) for quick identification of packages with abnormal class counts.
Consider disabling unnecessary serialization or reflection that triggers dynamic class generation.
Appendix
VisualVM download: https://visualvm.github.io/
OQL documentation: https://www.devdoc.net/javaxe/visualvm-1.3.7/oqlhelp.html
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
JD Retail Technology
Official platform of JD Retail Technology, delivering insightful R&D news and a deep look into the lives and work of technologists.
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.
