Package org.apache.sling.extensions.leakdetector.internal

Source Code of org.apache.sling.extensions.leakdetector.internal.LeakDetector$BundleReference

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sling.extensions.leakdetector.internal;

import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
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.
     * While analyzing the Heap Dump it might appear that GC roots of such classloaders (suspected)
     * points to LeakDetector. This happens because they are held here through PhantomReference
     * and there normal GC has not been done. So consider that as false positive
     */
    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
        BundleReference ref = null;
        while ((ref = (BundleReference)queue.poll()) != null){
            removeBundle(ref);
        }
    }

    private void removeBundle(BundleReference ref) {
        BundleInfo bi = bundleInfos.get(ref.bundleId);

        synchronized (leakDetectorLock){
            //bi cannot be null
            bi.decrementUsageCount(ref);
            refs.remove(ref);
            ref.clear();
        }

        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);
                }
            }
        }
        pw.println();

        addHelp(pw);
    }

    private static void addHelp(PrintWriter pw){
        RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean();
        List<String> argList = bean.getInputArguments();

        boolean containsRequiredArgs = argList.contains("-XX:+UseConcMarkSweepGC")
                && argList.contains("-XX:+CMSClassUnloadingEnabled");

        if(!containsRequiredArgs){
            pw.println("Required VM Options Missing");
            pw.println("===========================");
            pw.println("Leak detector relies on garbage collection of classloaders. By default");
            pw.println("the classloaders are not garbage collected. To enable garbage collection of");
            pw.println("classloader start the JVM with following options ");
            pw.println("");
            pw.println("    -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled");
        }
    }

    //~---------------------------------------<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();
        /**
         * The hashCode might collide for two different classloaders but then
         * we cannot keep a hard reference to Classloader reference. So at best
         * we keep the systemHashCode and *assume* it is unqiue at least wrt
         * classloader instances
         */
        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;
    }
}
TOP

Related Classes of org.apache.sling.extensions.leakdetector.internal.LeakDetector$BundleReference

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.