You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/10/18 23:24:40 UTC

[sling-org-apache-sling-extensions-classloader-leak-detector] 01/15: SLING-3359 - Classloader Leak Detector Console Tab

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-extensions-classloader-leak-detector.git

commit 14169705f4b4e5a772f4ead22cbd3ccb257bee4e
Author: Chetan Mehrotra <ch...@apache.org>
AuthorDate: Fri Jan 31 11:52:08 2014 +0000

    SLING-3359 - Classloader Leak Detector Console Tab
    
    Initial commit.
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1563112 13f79535-47bb-0310-9956-ffa450edef68
---
 README.md                                          |  27 ++
 pom.xml                                            |  83 ++++++
 .../leakdetector/internal/LeakDetector.java        | 304 +++++++++++++++++++++
 3 files changed, 414 insertions(+)

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3de6411
--- /dev/null
+++ b/README.md
@@ -0,0 +1,27 @@
+Sling Classloader Leak Detector
+===============================
+
+This bundle provides support for tracing classloader leaks which occur due to
+improper cleanup in bundles. Refer to [SLING-3359][1] for background details
+
+The bundle registers a Felix Configuration Printer which dumps out a list of
+suspected classloaders which are not getting garbage collected. it can be accessed
+at http://localhost:8080/system/console/status-leakdetector
+
+    Possible classloader leak detected
+    Number of suspicious bundles - 1
+
+    * org.apache.sling.sample.leakdetector.bad-bundle (0.0.1.SNAPSHOT) - Classloader Count [2]
+         - Bundle Id - 204
+         - Leaked classloaders
+             - Identity HashCode - 4a273519, Creation time 31.01.2014 15:22:58.407
+
+### JVM Arguments
+
+ By default on Oracle JDK the classloaders and related classes from Permgen are
+ not garbage collected by default. This bundle relies on Classloaders getting
+ gced for it work. So to enable that pass on following arguments
+
+     -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled
+
+[1]: https://issues.apache.org/jira/browse/SLING-3359
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..537b8e2
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<!--
+/*************************************************************************
+ *
+ * ADOBE CONFIDENTIAL
+ * __________________
+ *
+ *  Copyright 2012 Adobe Systems Incorporated
+ *  All Rights Reserved.
+ *
+ * NOTICE:  All information contained herein is, and remains
+ * the property of Adobe Systems Incorporated and its suppliers,
+ * if any.  The intellectual and technical concepts contained
+ * herein are proprietary to Adobe Systems Incorporated and its
+ * suppliers and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * from Adobe Systems Incorporated.
+ **************************************************************************/
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.sling</groupId>
+    <artifactId>sling</artifactId>
+    <version>18</version>
+  </parent>
+
+  <artifactId>org.apache.sling.extensions.classloader-leak-detector</artifactId>
+  <packaging>bundle</packaging>
+  <version>0.0.1-SNAPSHOT</version>
+
+  <name>Adobe Sling ClassLoader Leak Detector</name>
+  <description>
+    Provides a web console configuration printer to provide details around classloader leaks
+  </description>
+
+  <scm>
+    <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/leak-detector</connection>
+    <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/leak-detector
+    </developerConnection>
+    <url>http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/leak-detector</url>
+  </scm>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-Activator>org.apache.sling.extensions.leakdetector.internal.LeakDetector</Bundle-Activator>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <version>4.3.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+      <version>4.3.0</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/src/main/java/org/apache/sling/extensions/leakdetector/internal/LeakDetector.java b/src/main/java/org/apache/sling/extensions/leakdetector/internal/LeakDetector.java
new file mode 100644
index 0000000..38e4446
--- /dev/null
+++ b/src/main/java/org/apache/sling/extensions/leakdetector/internal/LeakDetector.java
@@ -0,0 +1,304 @@
+package org.apache.sling.extensions.leakdetector.internal;
+
+import java.io.PrintWriter;
+import java.lang.ref.PhantomReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.Constants;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.util.tracker.BundleTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LeakDetector implements Runnable, BundleActivator {
+    /**
+     * Set of PhantomReferences such that PhantomReference itself is not GC
+     */
+    private final Set<Reference<?>> refs = Collections.synchronizedSet(new HashSet<Reference<?>>());
+
+    /**
+     * Lock to control concurrent access to internal data structures
+     */
+    private final Object leakDetectorLock = new Object();
+
+    private final ReferenceQueue<ClassLoader> queue = new ReferenceQueue<ClassLoader>();
+
+    private final ConcurrentMap<Long, BundleInfo> bundleInfos = new ConcurrentHashMap<Long, BundleInfo>();
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private Thread referencePoller;
+
+    private BundleContext context;
+
+    private BundleTracker bundleTracker;
+
+    public void start(BundleContext context) {
+        this.context = context;
+        this.bundleTracker = new LeakDetectorBundleTracker(context);
+
+        referencePoller = new Thread(this, "Bundle Leak Detector Thread");
+        referencePoller.setDaemon(true);
+        referencePoller.start();
+
+        Dictionary<String,Object> printerProps = new Hashtable<String, Object>();
+        printerProps.put(Constants.SERVICE_VENDOR, "Apache Software Foundation");
+        printerProps.put(Constants.SERVICE_DESCRIPTION, "Sling Log Configuration Printer");
+        printerProps.put("felix.webconsole.label", "leakdetector");
+        printerProps.put("felix.webconsole.title", "Classloader Leak Detector");
+        printerProps.put("felix.webconsole.configprinter.modes", "always");
+
+        context.registerService(LeakDetector.class.getName(), this, printerProps);
+    }
+
+    public void stop(BundleContext context) {
+        this.bundleTracker.close();
+        referencePoller.interrupt();
+    }
+
+    private class LeakDetectorBundleTracker extends BundleTracker {
+        public LeakDetectorBundleTracker(BundleContext context) {
+            //Only listen for started
+            super(context, Bundle.ACTIVE, null);
+            this.open();
+        }
+
+        @Override
+        public Object addingBundle(Bundle bundle, BundleEvent event) {
+            synchronized (leakDetectorLock) {
+                registerBundle(bundle);
+            }
+            return bundle;
+        }
+    }
+
+    private void registerBundle(Bundle bundle) {
+        ClassLoader cl = getClassloader(bundle);
+        //cl would be null for Fragment bundle
+        if (cl != null) {
+            BundleReference ref = new BundleReference(bundle, cl);
+            refs.add(ref);
+
+            //Note that a bundle can be started multiple times
+            //for e.g. when refreshed So we need to account for that also
+            BundleInfo bi = bundleInfos.get(bundle.getBundleId());
+            if (bi == null) {
+                bi = new BundleInfo(bundle);
+                bundleInfos.put(bundle.getBundleId(), bi);
+            }
+            bi.incrementUsageCount(ref);
+            log.info("Registered bundle [{}] with Classloader [{}]", bi, ref.classloaderInfo);
+        }
+    }
+
+    //~----------------------------------------<GC Callback>
+
+    public void run() {
+        while (!Thread.currentThread().isInterrupted()) {
+            try {
+                BundleReference ref = (BundleReference) queue.remove();
+                if (ref != null) {
+                    removeBundle(ref);
+                }
+            } catch (InterruptedException e) {
+                break;
+            }
+        }
+
+        log.info("Shutting down reference collector for Classloader LeakDetector");
+        //Drain out the queue
+        while (queue.poll() != null);
+    }
+
+    private void removeBundle(BundleReference ref) {
+        BundleInfo bi = bundleInfos.get(ref.bundleId);
+
+        synchronized (leakDetectorLock){
+            //bi cannot be null
+            bi.decrementUsageCount(ref);
+            refs.remove(ref);
+        }
+
+        log.info("Detected garbage collection of bundle [{}] - Classloader [{}]", bi, ref.classloaderInfo);
+    }
+
+
+
+    //~---------------------------------------<Configuration Printer>
+
+    /**
+     * @see org.apache.felix.webconsole.ConfigurationPrinter#printConfiguration(java.io.PrintWriter)
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public void printConfiguration(PrintWriter pw) {
+        //Try to force GC
+        //TODO Should we do by default or let user do it explicitly via
+        //Felix Web Console
+        //System.gc();
+
+        Set<Long> activeBundleIds = new HashSet<Long>();
+        for (Bundle b : context.getBundles()) {
+            activeBundleIds.add(b.getBundleId());
+        }
+
+        List<BundleInfo> suspiciousBundles = new ArrayList<BundleInfo>(bundleInfos.values());
+        Iterator<BundleInfo> itr = suspiciousBundles.iterator();
+        while (itr.hasNext()) {
+            BundleInfo bi = itr.next();
+
+            //Filter out bundles which are active and have
+            //only one classloader created for them
+            if (bi.hasSingleInstance()
+                    && activeBundleIds.contains(bi.bundleId)) {
+                itr.remove();
+            }
+        }
+
+        if (suspiciousBundles.isEmpty()) {
+            pw.println("No classloader leak detected");
+        } else {
+            pw.println("Possible classloader leak detected");
+            pw.printf("Number of suspicious bundles - %d %n", suspiciousBundles.size());
+            pw.println();
+
+            final String tab = "    ";
+
+            for(BundleInfo bi : suspiciousBundles){
+                pw.printf("* %s %n", bi);
+                pw.printf("%s - Bundle Id - %d %n", tab, bi.bundleId);
+                pw.printf("%s - Leaked classloaders %n", tab);
+                for(ClassloaderInfo ci : bi.leakedClassloaders()){
+                    pw.printf("%s%s - %s %n", tab, tab, ci);
+                }
+            }
+        }
+    }
+
+    //~---------------------------------------<Data Model>
+
+    private static class BundleInfo {
+        final String symbolicName;
+        final String version;
+        final long bundleId;
+        private final Set<ClassloaderInfo> classloaderInfos =
+                Collections.synchronizedSet(new HashSet<ClassloaderInfo>());
+
+        public BundleInfo(Bundle b) {
+            this.symbolicName = b.getSymbolicName();
+            this.version = b.getVersion().toString();
+            this.bundleId = b.getBundleId();
+        }
+
+        public synchronized void incrementUsageCount(BundleReference ref) {
+            classloaderInfos.add(ref.classloaderInfo);
+        }
+
+        public synchronized void decrementUsageCount(BundleReference ref) {
+            classloaderInfos.remove(ref.classloaderInfo);
+        }
+
+        public synchronized boolean hasSingleInstance() {
+            return classloaderInfos.size() == 1;
+        }
+
+        public synchronized List<ClassloaderInfo> leakedClassloaders(){
+            if(hasSingleInstance()){
+                return new ArrayList<ClassloaderInfo>(classloaderInfos);
+            }else{
+                List<ClassloaderInfo> cis = new ArrayList<ClassloaderInfo>(classloaderInfos);
+                Collections.sort(cis);
+
+                //Leave out the latest classloader entry as that is
+                //associated with running bundle
+                return cis.subList(0, cis.size() - 1);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s (%s) - Classloader Count [%s]", symbolicName,
+                    version, classloaderInfos.size());
+        }
+    }
+
+    private static class ClassloaderInfo implements Comparable<ClassloaderInfo> {
+        final Long creationTime = System.currentTimeMillis();
+        final long systemHashCode;
+
+        private ClassloaderInfo(ClassLoader cl) {
+            this.systemHashCode = System.identityHashCode(cl);
+        }
+
+        public int compareTo(ClassloaderInfo o) {
+            return creationTime.compareTo(o.creationTime);
+        }
+
+        public String getAddress(){
+            return Long.toHexString(systemHashCode);
+        }
+
+        public String getCreationDate(){
+            SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss.SSS");
+            return dateFormat.format(new Date(creationTime));
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            ClassloaderInfo that = (ClassloaderInfo) o;
+
+            if (systemHashCode != that.systemHashCode) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            return (int) (systemHashCode ^ (systemHashCode >>> 32));
+        }
+
+        @Override
+        public String toString() {
+            return String.format("Identity HashCode - %s, Creation time %s", getAddress(), getCreationDate());
+        }
+    }
+
+    private class BundleReference extends PhantomReference<ClassLoader> {
+        final Long bundleId;
+        final ClassloaderInfo classloaderInfo;
+
+        public BundleReference(Bundle bundle, ClassLoader cl) {
+            super(cl, queue);
+            this.bundleId = bundle.getBundleId();
+            this.classloaderInfo = new ClassloaderInfo(cl);
+        }
+    }
+
+    private static ClassLoader getClassloader(Bundle b) {
+        //Somehow it fails to compile on JDK 7. Explicit cast helps
+        BundleWiring bw = (BundleWiring) b.adapt(BundleWiring.class);
+        if(bw != null){
+            return bw.getClassLoader();
+        }
+        return null;
+    }
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.