Summary
In a long-running JVM service that converts many HTML documents to PDF sequentially, post-GC live heap grows linearly and never plateaus, eventually causing OutOfMemoryError. Every conversion creates a fresh Configuration + HTMLDocument and disposes both in a finally block, exactly as the documentation prescribes, yet retained memory keeps climbing. A class histogram attributes essentially all of the growth to Aspose-internal static caches, not to application objects.
Minimal reproduction
A complete, self-contained repro (single Gradle dependency from your public Maven repo, no proprietary content):
GitHub - matthewisayan-cf/aspose-html-leak-repro · GitHub
./gradlew run --args="2000 100 30" # <conversions> <sampleEvery> <warmup>
The harness loops Converter.convertHTML over the same document, forces GC, and prints used (post-GC) heap plus a baseline-vs-final class histogram diff. It runs in evaluation mode out of the box (the leak reproduces with or without a license).
Environment
- Aspose.HTML for Java 26.5 (
com.aspose:aspose-html:26.5:jdk17) - JDK 17
- Reproduced on macOS (arm64) and Linux (x86_64)
Conversion code (per the docs — dispose in finally)
Configuration configuration = null;
HTMLDocument document = null;
try {
configuration = new Configuration();
document = new HTMLDocument(html, baseUrl, configuration);
PdfSaveOptions saveOptions = new PdfSaveOptions();
saveOptions.setTaggedPdf(true);
saveOptions.getPageSetup().setAnyPage(new Page(new Size(Unit.fromInches(8.5), Unit.fromInches(11))));
Converter.convertHTML(document, saveOptions, outputStream);
} finally {
if (document != null) document.dispose();
if (configuration != null) configuration.dispose();
}
Expected vs. actual
- Expected: because every
HTMLDocumentandConfigurationis disposed, post-GC live heap should reach a steady state and stay flat across thousands of identical conversions. - Actual: post-GC live heap rises linearly with no plateau; the process trends toward
OutOfMemoryError.
Observed — post-GC live heap (500-conversion sample, JDK 17)
| conversions | live heap (post-GC) |
|---|---|
| 0 | 64.6 MB |
| 100 | 82.0 MB |
| 200 | 98.2 MB |
| 300 | 113.4 MB |
| 400 | 130.8 MB |
| 500 | 145.9 MB |
≈ +167 KB retained per conversion, linear. An extended run with a larger document showed the same shape over 3000 conversions: 328 MB → 1148 MB (~280 KB/conversion, no plateau). Metaspace / compressed class space stay flat throughout, so this is heap object retention, not class-metadata growth.
Where the memory goes (class histogram diff, live objects)
deltaKB deltaInstances class
19968.2 851976 com.aspose.html.utils.aGE
19968.2 851976 com.aspose.html.utils.aGE$b
8812.3 281992 com.aspose.html.utils.ms.System.Drawing.Color
6609.2 281992 com.aspose.html.utils.aGD
6609.2 281992 com.aspose.html.utils.aKL
6609.2 281992 com.aspose.html.utils.aJR
3304.6 70498 com.aspose.html.utils.aKK
2423.3 103394 com.aspose.html.utils.ms.System.Collections.Generic.Dictionary$Link
2203.1 70498 com.aspose.html.utils.aLh
The growth set is essentially 100% com.aspose.html.utils.* (plus the embedded System.Drawing.Color). Instance counts scale linearly with the number of conversions — i.e. a fixed number of objects is retained per conversion and never released.
What we already tried
- Dispose
HTMLDocumentandConfigurationon every conversion (above). No effect on the trend. - A fresh
Configurationper conversion (not a shared/static one). - Forcing GC before each measurement, so this is retained (not merely uncollected) memory.
The only thing that fully reclaims the memory is tearing down and rebuilding the classloader that loaded Aspose (or restarting the process), which discards these static caches — consistent with the leak living in Aspose-internal static state.
Questions
- Is this a known issue, and is a fix targeted for an upcoming release?
- Is there a supported API to clear/flush these internal caches between conversions in a long-running process, so we don’t have to recycle the classloader/process?
- If not, what is the recommended pattern for high-volume, long-lived JVM services that must convert many documents without unbounded heap growth?