/* * JBoss, Home of Professional Open Source. * Copyright 2006, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.test.classloader.leak.clstore; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.PrintStream; import java.io.Serializable; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jboss.logging.Logger; import org.jboss.profiler.jvmti.ReferenceDataPoint; public class ClassLoaderStore { private static final Logger log = Logger.getLogger(ClassLoaderStore.class); private static ClassLoaderStore instance = new ClassLoaderStore(); private final Map<String, WeakReference<ClassLoader>> classloaders = new HashMap<String, WeakReference<ClassLoader>>(); private final int depth = Integer.parseInt(System.getProperty("jboss.classloader.leak.test.depth", "12")); private ClassLoaderStore() { } public static ClassLoaderStore getInstance() { return instance; } public void storeClassLoader(String key, ClassLoader loader) { log.debug("Storing " + loader + " under " + key); ClassLoader parent = loader.getParent(); while (parent != null) { log.debug("Parent is " + parent); parent = parent.getParent(); } WeakReference<ClassLoader> ref = new WeakReference<ClassLoader>(loader); classloaders.put(key, ref); } public ClassLoader getClassLoader(String key, boolean forceGC, String reportFile) { ClassLoader result = null; WeakReference<ClassLoader> ref = classloaders.get(key); if (ref != null) { result = (ClassLoader) ref.get(); if (result != null && forceGC) { try { result = null; // Don't hold a ref to it here while analyzing heap result = getClassLoader(ref, reportFile); } catch (Exception e) { log.error("Caught exception checking for classloader release", e); } } } return result; } /** * If you started your class with -agentlib:jbossAgent in case of leakage (if className still loaded) a file (reportFile) will be created, and a heapSnapshot(./snapshot,mem) * * @param weakReferenceOnLoader A weakReference to the created ClassLoader. If there is no references to this classLoader this reference will be cleared * @param className The class name supposed to be unloade. * @param reportHTMLFile the report file * @throws Exception */ private ClassLoader getClassLoader(WeakReference<ClassLoader> weakReferenceOnLoader, String reportHTMLFile) throws Exception { LeakAnalyzer leakAnalyzer = null; try { leakAnalyzer = new LeakAnalyzer(); } catch (Throwable t) { log.debug("Could not instantiate JVMTIInterface:" + t.getLocalizedMessage()); } if (leakAnalyzer != null && leakAnalyzer.isActive()) { leakAnalyzer.forceGC(); if (weakReferenceOnLoader.get() == null) { log.debug("leakAnalyzer.forceGC() released CL ref"); return null; } fillMemory(weakReferenceOnLoader); if (weakReferenceOnLoader.get() == null) { return null; } // Sleep a bit to allow CPU to do work like exchange cluster PING responses Thread.sleep(20); leakAnalyzer.heapSnapshot("snapshot", "mem"); if (weakReferenceOnLoader.get() == null) { log.debug("leakAnalyzer.heapSnapshot() released CL ref"); return null; } Thread.sleep(20); @SuppressWarnings("unchecked") HashMap<Long, List<ReferenceDataPoint>> datapoints = leakAnalyzer.createIndexMatrix(); if (weakReferenceOnLoader.get() == null) { log.debug("leakAnalyzer.createIndexMatrix() released CL ref"); return null; } Thread.sleep(20); String report = leakAnalyzer.exploreObjectReferences(datapoints, weakReferenceOnLoader.get(), this.depth, true, false); log.info(report); if (reportHTMLFile != null) { File outputfile = new File(reportHTMLFile); FileOutputStream outfile = new FileOutputStream(outputfile); PrintStream realoutput = new PrintStream(outfile); realoutput.println(report); realoutput.close(); } Thread.sleep(20); leakAnalyzer.forceGC(); } else { log.debug("JVMTI not active; using System.gc()"); System.gc(); Thread.sleep(1000); if (weakReferenceOnLoader.get() != null) fillMemory(weakReferenceOnLoader); if (weakReferenceOnLoader.get() != null) fillMemory(weakReferenceOnLoader); } return (ClassLoader) weakReferenceOnLoader.get(); } private void fillMemory(WeakReference<ClassLoader> ref) { Runtime rt = Runtime.getRuntime(); int[] adds = { 0, 10, 20, 30, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49 }; for (int i = 0; i < adds.length; i++) { int toAdd = adds[i]; System.gc(); System.runFinalization(); if (ref.get() == null) break; // create garbage, filling a larger and larger % of // free memory on each loop byte[][] bytez = new byte[10000][]; long avail = rt.freeMemory(); int create = (int) (avail / 1000 * (950 + toAdd)); String pct = (95 + (toAdd/10)) + "." + (toAdd - ((toAdd/10) * 10)); int bucket = create / 10000; log.info("Filling " + pct + "% of free memory. Free memory=" + avail + " Total Memory=" + rt.totalMemory() + " Max Memory=" + rt.maxMemory()); try { for (int j = 0; j < bytez.length; j++) { bytez[j] = new byte[bucket]; if (j % 100 == 0 && ref.get() == null) { return; } } } catch (Throwable t) { bytez = null; System.gc(); System.runFinalization(); log.warn("Caught throwable filling memory: " + t); break; } finally { bytez = null; // Sleep a bit to allow CPU to do work like exchange cluster PING responses try { Thread.sleep(20); } catch (InterruptedException ignored) { log.warn("Interrupted"); break; } } } try { ByteArrayOutputStream byteout = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteout); out.writeObject(new Dummy()); out.close(); ByteArrayInputStream byteInput = new ByteArrayInputStream(byteout.toByteArray()); ObjectInputStream input = new ObjectInputStream(byteInput); input.readObject(); input.close(); } catch (Exception e) { e.printStackTrace(); } if (ref.get() != null) { System.gc(); System.runFinalization(); } } public void removeClassLoader(String key) { classloaders.remove(key); } /** Used just to serialize anything and release SoftCache on java Serialization */ private static class Dummy implements Serializable { private static final long serialVersionUID = 1L; } }