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.