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>.