Heap Dump Analysis with Eclipse MAT in Java

1. Generating Heap Dumps

Programmatic Heap Dump Generation

// HeapDumpGenerator.java
import com.sun.management.HotSpotDiagnosticMXBean;
import javax.management.MBeanServer;
import java.lang.management.ManagementFactory;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
public class HeapDumpGenerator {
private static final String HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic";
private static volatile HotSpotDiagnosticMXBean hotspotMBean;
public static void generateHeapDump(String fileName, boolean live) {
try {
if (hotspotMBean == null) {
synchronized (HeapDumpGenerator.class) {
if (hotspotMBean == null) {
hotspotMBean = getHotSpotDiagnosticMXBean();
}
}
}
File file = new File(fileName);
if (file.exists()) {
file.delete();
}
hotspotMBean.dumpHeap(fileName, live);
System.out.println("Heap dump generated: " + fileName);
} catch (IOException e) {
throw new RuntimeException("Failed to generate heap dump: " + fileName, e);
}
}
private static HotSpotDiagnosticMXBean getHotSpotDiagnosticMXBean() {
try {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
HotSpotDiagnosticMXBean bean = ManagementFactory.newPlatformMXBeanProxy(
server, HOTSPOT_BEAN_NAME, HotSpotDiagnosticMXBean.class);
return bean;
} catch (IOException e) {
throw new RuntimeException("Failed to get HotSpot Diagnostic MXBean", e);
}
}
public static String generateHeapDumpWithTimestamp(boolean live) {
String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
String fileName = "heapdump-" + timestamp + (live ? "-live" : "") + ".hprof";
generateHeapDump(fileName, live);
return fileName;
}
// Alternative method using JMX
public static void triggerHeapDumpViaJMX() {
try {
String name = ManagementFactory.getRuntimeMXBean().getName();
String pid = name.split("@")[0];
// Using jmap equivalent via JMX
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName on = new ObjectName("com.sun.management:type=DiagnosticCommand");
String operation = "gcClassHistogram";
Object[] params = new Object[] { new String[] { "-all" } };
String[] signature = new String[] { String[].class.getName() };
Object result = mbs.invoke(on, operation, params, signature);
System.out.println("GC Class Histogram:\n" + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}

JVM Options for Heap Dump Generation

// JVMOptionsExamples.java
public class JVMOptionsExamples {
/*
Common JVM options for heap dump analysis:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./heapdumps/
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:./logs/gc.log
-XX:+UseG1GC
-Xmx512m -Xms256m
For continuous monitoring:
-XX:+HeapDumpBeforeFullGC
-XX:+HeapDumpAfterFullGC
For specific events:
-XX:OnOutOfMemoryError="jmap -dump:format=b,file=heapdump.hprof %p"
*/
}

2. Memory Leak Examples

Common Memory Leak Patterns

// MemoryLeakExamples.java
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class MemoryLeakExamples {
// Example 1: Static collections causing memory leaks
private static final Map<String, Object> STATIC_CACHE = new HashMap<>();
private static final List<byte[]> STATIC_DATA = new ArrayList<>();
public static void addToStaticCache(String key, Object value) {
STATIC_CACHE.put(key, value);
}
public static void loadLargeData() {
for (int i = 0; i < 1000; i++) {
STATIC_DATA.add(new byte[1024 * 1024]); // 1MB each
}
}
// Example 2: Unclosed resources
public static class ResourceHolder {
private byte[] data = new byte[1024 * 1024]; // 1MB
public void close() {
// Simulate resource cleanup
data = null;
}
}
private static final List<ResourceHolder> LEAKING_RESOURCES = new ArrayList<>();
public static void createLeakingResources() {
for (int i = 0; i < 100; i++) {
ResourceHolder holder = new ResourceHolder();
LEAKING_RESOURCES.add(holder);
// Forgot to call holder.close()
}
}
// Example 3: Listeners and callbacks
public static class EventManager {
private final List<Runnable> listeners = new ArrayList<>();
public void addListener(Runnable listener) {
listeners.add(listener);
}
// Missing removeListener method
}
// Example 4: ThreadLocal misuse
private static final ThreadLocal<byte[]> THREAD_LOCAL_DATA = 
ThreadLocal.withInitial(() -> new byte[1024 * 1024]); // 1MB per thread
public static void useThreadLocal() {
byte[] data = THREAD_LOCAL_DATA.get();
// Data remains until thread dies or ThreadLocal is cleared
}
// Example 5: String intern misuse
public static void internAllStrings() {
List<String> internedStrings = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
String str = new String("String_" + i);
internedStrings.add(str.intern()); // Goes to PermGen/Metaspace
}
}
}
// Example 6: Classloader leak
public class ClassloaderLeakExample {
private static final Map<String, Class<?>> CLASS_CACHE = new ConcurrentHashMap<>();
public static class LeakingClassloader extends ClassLoader {
public Class<?> defineAndCacheClass(String name, byte[] bytecode) {
Class<?> clazz = defineClass(name, bytecode, 0, bytecode.length);
CLASS_CACHE.put(name, clazz);
return clazz;
}
}
}
// Example 7: Cache without eviction
public class InfiniteCache<K, V> {
private final Map<K, V> cache = new HashMap<>();
public void put(K key, V value) {
cache.put(key, value);
// No eviction policy - grows indefinitely
}
public V get(K key) {
return cache.get(key);
}
}

3. Memory Analysis Utilities

// MemoryAnalysisUtils.java
import java.lang.management.*;
import java.util.*;
public class MemoryAnalysisUtils {
public static void printMemoryStats() {
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage();
MemoryUsage nonHeapUsage = memoryMXBean.getNonHeapMemoryUsage();
System.out.println("=== Heap Memory Usage ===");
System.out.printf("Init: %,d bytes%n", heapUsage.getInit());
System.out.printf("Used: %,d bytes%n", heapUsage.getUsed());
System.out.printf("Committed: %,d bytes%n", heapUsage.getCommitted());
System.out.printf("Max: %,d bytes%n", heapUsage.getMax());
System.out.println("=== Non-Heap Memory Usage ===");
System.out.printf("Init: %,d bytes%n", nonHeapUsage.getInit());
System.out.printf("Used: %,d bytes%n", nonHeapUsage.getUsed());
System.out.printf("Committed: %,d bytes%n", nonHeapUsage.getCommitted());
System.out.printf("Max: %,d bytes%n", nonHeapUsage.getMax());
// Print garbage collector information
List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean gcBean : gcBeans) {
System.out.printf("GC: %s - Collections: %,d, Time: %,d ms%n",
gcBean.getName(), gcBean.getCollectionCount(), gcBean.getCollectionTime());
}
}
public static void printClassLoadingStats() {
ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
System.out.println("=== Class Loading Stats ===");
System.out.printf("Loaded: %,d classes%n", classLoadingMXBean.getLoadedClassCount());
System.out.printf("Total Loaded: %,d classes%n", classLoadingMXBean.getTotalLoadedClassCount());
System.out.printf("Unloaded: %,d classes%n", classLoadingMXBean.getUnloadedClassCount());
}
public static void printThreadStats() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
System.out.println("=== Thread Stats ===");
System.out.printf("Thread Count: %d%n", threadMXBean.getThreadCount());
System.out.printf("Peak Thread Count: %d%n", threadMXBean.getPeakThreadCount());
System.out.printf("Total Started Threads: %d%n", threadMXBean.getTotalStartedThreadCount());
// Print thread details
long[] threadIds = threadMXBean.getAllThreadIds();
for (long threadId : threadIds) {
ThreadInfo threadInfo = threadMXBean.getThreadInfo(threadId);
if (threadInfo != null) {
System.out.printf("Thread: %s - State: %s%n",
threadInfo.getThreadName(), threadInfo.getThreadState());
}
}
}
public static void forceGarbageCollection() {
System.gc();
System.out.println("Garbage collection requested");
}
public static void createMemoryPressure() {
System.out.println("Creating memory pressure...");
List<byte[]> memoryHog = new ArrayList<>();
try {
for (int i = 0; i < 100; i++) {
memoryHog.add(new byte[1024 * 1024]); // 1MB chunks
Thread.sleep(10);
}
} catch (OutOfMemoryError e) {
System.out.println("OutOfMemoryError triggered!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
memoryHog.clear();
forceGarbageCollection();
}
}
}

4. MAT Analysis Queries and Patterns

// MATAnalysisPatterns.java
public class MATAnalysisPatterns {
/*
Common MAT Analysis Patterns:
1. Dominator Tree Analysis:
- Identify which objects retain the most memory
- Look for unexpected large object retention
2. Histogram by Class:
- Group objects by class and see total memory usage
- Identify classes with too many instances
3. Leak Suspects Report:
- MAT's automated leak detection
- Shows potential memory leak suspects
4. Path to GC Roots:
- Find why objects aren't being garbage collected
- Exclude weak/soft/phantom references
5. OQL (Object Query Language) Examples:
*/
// Common OQL Queries:
String[] oqlQueries = {
// Find all instances of a specific class
"SELECT * FROM com.example.YourClass",
// Find objects retaining more than 1MB
"SELECT * FROM java.lang.Object WHERE @retainedHeapSize > 1000000",
// Find arrays larger than specific size
"SELECT * FROM byte[] WHERE @retainedHeapSize > 1048576",
// Find String objects with specific content
"SELECT * FROM java.lang.String s WHERE s.toString().startsWith('ERROR')",
// Find collections with many elements
"SELECT * FROM java.util.ArrayList WHERE elementData.@length > 1000",
// Find duplicate strings
"SELECT s.toString() FROM java.lang.String s GROUP BY s.toString() HAVING COUNT(*) > 1",
// Find objects by package
"SELECT * FROM INSTANCEOF com.yourpackage.*"
};
}
// MAT Report Interpretation Guide
class MATReportGuide {
/*
Key MAT Reports and Their Meaning:
1. Overview Report:
- Total heap size
- Number of objects
- Biggest objects by retained size
2. Histogram:
- Objects grouped by class
- Shallow vs retained size
- Look for unexpected large counts
3. Dominator Tree:
- Objects that retain large portions of heap
- Identify memory ownership hierarchy
4. Top Consumers:
- Packages/classes using most memory
- Helps identify problematic areas
5. Duplicate Classes:
- Multiple versions of same class
- Indicates classloader issues
*/
}

5. Automated Heap Analysis

// AutomatedHeapAnalyzer.java
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
public class AutomatedHeapAnalyzer {
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private final String outputDirectory;
public AutomatedHeapAnalyzer(String outputDirectory) {
this.outputDirectory = outputDirectory;
createOutputDirectory();
}
public void startPeriodicHeapDumps(long interval, TimeUnit unit) {
scheduler.scheduleAtFixedRate(this::generateHeapDump, 0, interval, unit);
}
public void stop() {
scheduler.shutdown();
}
private void generateHeapDump() {
try {
String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
String fileName = outputDirectory + "/heapdump-" + timestamp + ".hprof";
HeapDumpGenerator.generateHeapDump(fileName, true);
// Analyze basic metrics
analyzeHeapDumpMetrics(fileName);
} catch (Exception e) {
System.err.println("Error generating heap dump: " + e.getMessage());
}
}
private void analyzeHeapDumpMetrics(String fileName) {
// Basic file analysis without full MAT integration
File file = new File(fileName);
if (file.exists()) {
System.out.printf("Heap dump created: %s (%,d bytes)%n", 
fileName, file.length());
}
}
private void createOutputDirectory() {
try {
Files.createDirectories(Paths.get(outputDirectory));
} catch (IOException e) {
throw new RuntimeException("Failed to create output directory: " + outputDirectory, e);
}
}
public static void monitorMemoryUsage() {
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage();
double usagePercent = (double) heapUsage.getUsed() / heapUsage.getCommitted() * 100;
System.out.printf("Memory Usage: %.2f%% (%,d/%,d bytes)%n",
usagePercent, heapUsage.getUsed(), heapUsage.getCommitted());
// Trigger heap dump if usage is high
if (usagePercent > 80.0) {
System.out.println("High memory usage detected, generating heap dump...");
HeapDumpGenerator.generateHeapDumpWithTimestamp(true);
}
}
}, 0, 30000); // Check every 30 seconds
}
}

6. Example Application with Memory Issues

// LeakyApplication.java
import java.util.*;
import java.util.concurrent.*;
public class LeakyApplication {
private final Map<String, List<byte[]>> userSessions = new ConcurrentHashMap<>();
private final EventBus eventBus = new EventBus();
private final CacheManager cacheManager = new CacheManager();
public static void main(String[] args) throws InterruptedException {
LeakyApplication app = new LeakyApplication();
// Start memory monitoring
AutomatedHeapAnalyzer.monitorMemoryUsage();
// Generate initial heap dump
HeapDumpGenerator.generateHeapDumpWithTimestamp(true);
// Simulate application workload
app.simulateWorkload();
// Generate final heap dump
HeapDumpGenerator.generateHeapDumpWithTimestamp(true);
}
public void simulateWorkload() throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
final int userId = i;
executor.submit(() -> processUserRequest("user_" + userId));
if (i % 100 == 0) {
Thread.sleep(100);
MemoryAnalysisUtils.printMemoryStats();
}
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
}
private void processUserRequest(String userId) {
// Simulate memory leak: user sessions never cleaned up
List<byte[]> sessionData = userSessions.computeIfAbsent(userId, 
k -> new ArrayList<>());
// Add some data to session
sessionData.add(new byte[1024 * 50]); // 50KB per request
// Register listener (potential leak)
eventBus.register(new EventListener(userId));
// Cache data (potential leak)
cacheManager.cacheUserData(userId, generateUserData());
}
private byte[] generateUserData() {
return new byte[1024 * 10]; // 10KB user data
}
// Inner classes that might cause leaks
public class EventListener {
private final String userId;
private final byte[] contextData = new byte[1024 * 5]; // 5KB
public EventListener(String userId) {
this.userId = userId;
}
public void onEvent(String event) {
// Event handling logic
}
}
public static class EventBus {
private final List<Object> listeners = new ArrayList<>();
public void register(Object listener) {
listeners.add(listener);
// Missing unregister method
}
}
public static class CacheManager {
private final Map<String, Object> cache = new ConcurrentHashMap<>();
public void cacheUserData(String userId, Object data) {
cache.put(userId, data);
// No eviction policy
}
}
}

7. Analysis Script and Automation

// HeapAnalysisRunner.java
import java.util.*;
public class HeapAnalysisRunner {
public static void main(String[] args) {
System.out.println("=== Starting Heap Analysis Demo ===");
// Print initial memory stats
MemoryAnalysisUtils.printMemoryStats();
MemoryAnalysisUtils.printClassLoadingStats();
// Create memory pressure to trigger interesting heap state
System.out.println("\n=== Creating Memory Pressure ===");
MemoryAnalysisUtils.createMemoryPressure();
// Generate heap dump for analysis
System.out.println("\n=== Generating Heap Dump ===");
String heapDumpFile = HeapDumpGenerator.generateHeapDumpWithTimestamp(true);
// Print final stats
System.out.println("\n=== Final Memory Stats ===");
MemoryAnalysisUtils.printMemoryStats();
MemoryAnalysisUtils.forceGarbageCollection();
System.out.println("\n=== Analysis Complete ===");
System.out.println("Heap dump file: " + heapDumpFile);
System.out.println("Use Eclipse MAT to analyze the heap dump:");
System.out.println("1. Open " + heapDumpFile + " in MAT");
System.out.println("2. Run Leak Suspects Report");
System.out.println("3. Check Dominator Tree");
System.out.println("4. Analyze Histogram by Class");
}
}

Key MAT Analysis Techniques:

  1. Leak Suspects Report: MAT's automated leak detection
  2. Dominator Tree: Identify objects retaining the most memory
  3. Histogram: Group objects by class and analyze memory usage
  4. Path to GC Roots: Find why objects aren't being garbage collected
  5. OQL Queries: Custom queries for specific analysis needs
  6. Compare Heap Dumps: Identify changes between two heap dumps

Best Practices:

  1. Always take heap dumps with live=true for accurate analysis
  2. Take multiple heap dumps over time to identify trends
  3. Use appropriate JVM flags for automatic heap dump generation
  4. Analyze both shallow and retained heap sizes
  5. Look for unexpected object retention and large object arrays

This comprehensive approach will help you effectively identify and resolve memory issues in your Java applications using Eclipse MAT.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper