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:
- Leak Suspects Report: MAT's automated leak detection
- Dominator Tree: Identify objects retaining the most memory
- Histogram: Group objects by class and analyze memory usage
- Path to GC Roots: Find why objects aren't being garbage collected
- OQL Queries: Custom queries for specific analysis needs
- Compare Heap Dumps: Identify changes between two heap dumps
Best Practices:
- Always take heap dumps with
live=truefor accurate analysis - Take multiple heap dumps over time to identify trends
- Use appropriate JVM flags for automatic heap dump generation
- Analyze both shallow and retained heap sizes
- 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.