📌 Note: This is continuation of my Heap Dump subject. If you want to see the full evaluation matrix of available Heap Dump CLI analysis tools, read: How to Automate Analysis of Huge JVM Heap Dump Files.
In my previous post, I established why rewriting a heap analyzer from scratch is a multi-month architectural trap and why Eclipse MAT’s optimized, memory-mapped indexing framework was the choice for handling massive .hprof files. Now, it’s time to move from theory to an example implementation.
Disclaimer: as I already mentioned I have not used it in the production environment yet.
To build a fully automated diagnostics pipeline—whether for a continuous integration environment or an autonomous monitoring sidecar—we must run MAT completely headless.
The Java implementation below demonstrates how to programmatically orchestrate Eclipse MAT using ProcessBuilder. The code below: invokes the headless CLI binary, passes critical JVM tuning arguments required for large-scale production dumps, and dynamically extracts and inspects the resulting compressed HTML reports on-the-fly to deliver a definitive binary verdict: Is there a memory leak or not?
Key Implementation Details:
Headless Orchestration: Uses org.eclipse.mat.api.parse to trigger out-of-process report generation.
On-the-Fly Zip Parsing: Avoids disk overhead by utilizing ZipFile and Scanner to inspect index.html or content.html directly inside the generated archive for MAT’s signature “Problem Suspect” marker. Please notice, that those files can be used for further analysis – if we not only want whether there is a memory leak, but where it is.
Scale Considerations: For production heaps exceeding 40 GB, ensure you uncomment the high-performance VM arguments (-Xmx, -XX:+UseG1GC, and -Dorg.eclipse.mat.api:max_index_size=long) noted in the comments to prevent the analyzer itself from running out of memory.
Here is the complete, self-contained analyzer class, with some comments:
import java.io.*;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.Scanner;
/**
* There are scenarios where the code provided will say "No memory leak detected" even though your
* app is slowly dying:
*
* 1. The "Slow Drip": If a leak is very small (e.g., leaking 1KB every hour), and you take a heap
* dump after only 2 hours, MAT won't flag it as a "Problem Suspect" because it doesn't represent
* a significant portion of the heap yet.
*
* 2. Distributed Leaks: If the leak is spread across 10,000 different small objects rather than
* one giant collection, MAT’s "Dominator Tree" might not see a single clear "suspect" to blame.
*
* 3. High Churn (Not a Leak): Your app might be slow because it's creating millions of temporary
* objects (GC Pressure), but since they can be collected, MAT won't flag them as a leak.
*
* 4. Native Memory: MAT only analyzes the Java Heap. If your leak is happening in native C++ code
* (via JNI) or in the Metaspace (class loading), this script will show 0% leakage.
*
* We can simulate below program by calling:
* ../../mat/MemoryAnalyzer -consolelog -application org.eclipse.mat.api.parse java_pid120191.hprof
* org.eclipse.mat.api:suspects
*
* How to get closer to 100%
* To be truly sure, the "Gold Standard" isn't just one report; it's Comparison:
*
* Take dump_A.hprof.
*
* Perform some work/load testing.
*
* Take dump_B.hprof.
*
* Use MAT to compare the two. If the number of instances of a specific class increased and never went back down, you have a confirmed leak, regardless of whether it's "big" enough to be a "Problem Suspect."
*/
public class HeapAnalyzer {
// Path to MAT CLI executable - adapt to your locations
static String MAT_PATH = "/home/user/Desktop/work/MemoryLeaks/mat/MemoryAnalyzer";
static String MAT_ROOT = "/home/user/Desktop/work/MemoryLeaks/mat";
static String HPROF_FILE = "/home/user/Desktop/work/MemoryLeaks/MemoryAnalyzerWithMAT/hprof/java_pid120191.hprof";
public static void main(String[] args) throws Exception {
File heapDump = new File(HPROF_FILE);
if (!heapDump.exists()) {
System.out.println("Heap dump not found: " + heapDump.getAbsolutePath());
return;
}
String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
System.out.println("Starting MAT Analysis for: " + heapDump.getName());
// Consolidated Call: Parse AND Report
// Using 'org.eclipse.mat.api.parse' + 'org.eclipse.mat.api:suspects'
// is the most robust way to trigger report generation.
ProcessBuilder pb = new ProcessBuilder(
MAT_PATH,
"-vm", javaBin,
"-clean",
// "-configuration", tempConfig.toAbsolutePath().toString(),
"-consolelog",
"-application", "org.eclipse.mat.api.parse",
heapDump.getAbsolutePath(),
"org.eclipse.mat.api:suspects"
);
// For huge files
// "-vmargs", // Tell MAT that JVM arguments follow
// "-Xmx64g", // Give it enough RAM to build the index (check also memory in MemoryAnalyzer.ini)
// "-XX:+UseG1GC" // Use a modern Garbage Collector for the analyzer
// "-Dkeep_unreachable_objects=false", // Slims down the analysis
// "-Dorg.eclipse.mat.api:max_index_size=long" // Necessary for massive object counts
// 40Gb might need 100Gb of RAM (dump 40Gb, indexes also 30-40Gb); on SSD it parsing might take 30 minutes; on normal HD hours
// Set the working directory - place where configuration etc is
pb.directory(new File(MAT_ROOT));
// MAT’s stdout/stderr is printed to the console
pb.inheritIO();
Process process = pb.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
System.err.println("MAT process failed with exit code: " + exitCode);
return;
}
// Locate the Generated ZIP
// MAT usually names it: [HPROF_NAME]_Leak_Suspects.zip
String name = heapDump.getName();
String baseName = name.contains(".") ? name.substring(0, name.lastIndexOf('.')) : name;
File reportZip = new File(heapDump.getParent(), baseName + "_Leak_Suspects.zip");
if (!reportZip.exists()) {
System.err.println("Could not find the generated report zip: " + reportZip.getAbsolutePath());
return;
}
// Leak Detection
analyzeLeakZip(reportZip);
}
private static void analyzeLeakZip(File zipFile) {
System.out.println("Analyzing report for leak indicators...");
try (ZipFile zip = new ZipFile(zipFile)) {
// We look for 'index.html' which summarizes suspects
ZipEntry indexEntry = zip.getEntry("index.html");
if (indexEntry == null) {
// If index isn't there, check for the suspects content page
indexEntry = zip.getEntry("pages/content.html");
}
if (indexEntry != null) {
try (InputStream is = zip.getInputStream(indexEntry);
Scanner scanner = new Scanner(is)) {
boolean confirmedLeak = false;
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
// "Problem Suspect" is the key phrase MAT uses in HTML
// when it finds a significant memory accumulator.
if (line.contains("Problem Suspect")) {
confirmedLeak = true;
break;
}
}
if (confirmedLeak) {
System.out.println("RESULT: MEMORY LEAK SUSPECTED!");
System.out.println("Please review archive containing summarization: \n" + zipFile.getAbsolutePath());
} else {
System.out.println("RESULT: No significant leak suspects identified.");
}
}
}
} catch (IOException e) {
System.err.println("Error reading ZIP file: " + e.getMessage());
}
}
}
Below are some advises for parsing huge files:
- Increase MAT’s Own Memory (The .ini File)
The most common mistake is trying to analyze a 40GB dump while MAT only has 1GB of heap allocated to itself. MAT needs roughly 1.2x to 1.5x the size of the heap dump in RAM to build its indexes efficiently.
Locate MemoryAnalyzer.ini.
Change the -Xmx setting to at least 48g or 64g.
Ensure you are running on a machine (or cloud instance) that actually has that much physical RAM.
Plaintext
-vmargs
-Xmx64g
-XX:+UseG1GC
- Use “Parser Sidekick” (Headless Parsing)
Never try to open a 40GB file directly in the MAT GUI. The GUI will hang during the indexing phase. Instead, use your script (or the CLI) to pre-parse the indexes on a powerful server.
Once the CLI finishes, it generates several .index files and a .zip report. You can then:
Read the HTML report (no RAM required).
Open the .hprof in the GUI after the indexes are built. It will open instantly because the heavy lifting is already done.
- Enable 64-bit Indexing
For dumps larger than 32GB, the number of objects might exceed the limits of standard 32-bit addressing. Ensure your MemoryAnalyzer.ini includes:
-Dorg.eclipse.mat.api:max_index_size=long (if supported by your version) to handle the massive object count.