google-site-verification: google61fe8ba583a51912.html
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