glassfish
  1. glassfish
  2. GLASSFISH-20970

Classloader leak in PoolResizeTimerTask

    Details

    • Type: Bug Bug
    • Status: Open
    • Priority: Critical Critical
    • Resolution: Unresolved
    • Affects Version/s: 4.0
    • Fix Version/s: None
    • Component/s: ejb_container
    • Labels:
      None

      Description

      There is a classloader leak in the com.sun.ejb.containers.util.pool.NonBlockingPool and com.sun.ejb.containers.util.pool.NonBlockingPool$PoolResizeTimerTask.

      When a application is re-deployed instances of these two classes are created as described in https://java.net/jira/browse/GLASSFISH-16676

      This also leads to a classloader leak, which eventually prevents re-deployment of applications.

        Activity

        Hide
        electricsam added a comment -

        Here is the reference tree to the GC root:

        this - EarClassLoader

        • parent - WebappClassLoader
          • <classloader> - GenericEJBHome_Generated
            • cls - PresentationManagerImpl$ClassDataImpl
              • value - WeakHashMapSafeReadLock$Entry
                • [] - WeakHashMapSafeReadLock$Entry[]
                  • table - WeakHashMapSafeReadLock
                    • map - PresentationManagerImpl$1
                      • classToClassData - PresentationManagerImpl
                        • presentationMgr - POAProtocolMgr
                          • protocolMgr - StatelessSessionContainer
                            • this$0 - StatelessSessionContainer$SessionContextFactory
                              • factory - NonBlockingPool
                                • this$0 - NonBlockingPool$PoolResizeTimerTask
        Show
        electricsam added a comment - Here is the reference tree to the GC root: this - EarClassLoader parent - WebappClassLoader <classloader> - GenericEJBHome_Generated cls - PresentationManagerImpl$ClassDataImpl value - WeakHashMapSafeReadLock$Entry [] - WeakHashMapSafeReadLock$Entry[] table - WeakHashMapSafeReadLock map - PresentationManagerImpl$1 classToClassData - PresentationManagerImpl presentationMgr - POAProtocolMgr protocolMgr - StatelessSessionContainer this$0 - StatelessSessionContainer$SessionContextFactory factory - NonBlockingPool this$0 - NonBlockingPool$PoolResizeTimerTask
        Hide
        electricsam added a comment -

        I think the actual issue is that there is a hard reference in the dictionary map in PresentationManagerImpl$ClassDataImpl

        Here is some cleanup code that I've tried running in the @PreDestroy method of a singleton startup bean to try to clean up classloader leaks. This seems to get rid of many of them. This is just test code.

        public abstract class ClassLoaderCleaner {
        
            private static final Logger logger = Logger.getLogger(ClassLoaderCleaner.class.getName());
        
            private ClassLoader loader = null;
        
            protected void destroy() {
                try {
                    loader = getClass().getClassLoader();
                    cleanUp();
                } catch (Throwable e) {
                    logger.log(Level.SEVERE, null, e);
                }
            }
        
            private Thread[] getThreads() {
                ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
                ThreadGroup parentGroup;
                while ((parentGroup = rootGroup.getParent()) != null) {
                    rootGroup = parentGroup;
                }
        
                Thread[] threads = new Thread[rootGroup.activeCount()];
                while (rootGroup.enumerate(threads, true) == threads.length) {
                    threads = new Thread[threads.length * 2];
                }
                return threads;
            }
        
            private boolean loaderRemovable(ClassLoader cl) {
                if (cl == null) {
                    return false;
                }
                Object isDoneCalled = getObject(cl, "doneCalled");
                if (cl.getClass().getName().equals(loader.getClass().getName())
                        && isDoneCalled instanceof Boolean && (Boolean) isDoneCalled) {
                    return true;
                }
                return loader == cl;
            }
        
            private Field getField(Class clazz, String fieldName) {
                Field f = null;
                try {
                    f = clazz.getDeclaredField(fieldName);
                } catch (NoSuchFieldException ex) {
        
                } catch (SecurityException ex) {
                    logger.log(Level.WARNING, "Unable to get field " + fieldName + " on " + clazz.getName(), ex);
                }
        
                if (f == null) {
                    Class parent = clazz.getSuperclass();
                    if (parent != null) {
                        f = getField(parent, fieldName);
                    }
                }
                if (f != null) {
                    f.setAccessible(true);
                }
                return f;
            }
        
            private Object getObject(Object instance, String fieldName) {
                Class clazz = instance.getClass();
                Field f = getField(clazz, fieldName);
                if (f != null) {
                    try {
                        return f.get(instance);
                    } catch (IllegalArgumentException | IllegalAccessException ex) {
                        logger.log(Level.WARNING, "Unable to get " + fieldName + " on " + clazz.getName(), ex);
                    }
                }
                return null;
            }
        
            private void cleanUp() {
                Thread[] threads = getThreads();
                for (Thread thread : threads) {
                    if (thread != null) {
                        cleanContextClassLoader(thread);
                        cleanOrb(thread);
                        cleanThreadLocal(thread);
                        cleanTimers(thread);
                    }
        
                }
            }
        
            private void cleanContextClassLoader(Thread thread) {
                if (loaderRemovable(thread.getContextClassLoader())) {
                    thread.setContextClassLoader(null);
                    logger.log(Level.INFO, "Cleaned context classloader {0}", thread.getName());
                }
            }
        
            private void cleanOrb(Thread thread) {
                Object currentWork = getObject(thread, "currentWork");
                if (currentWork != null) {
                    Object orb = getObject(currentWork, "orb");
                    if (orb != null) {
                        Object transportManager = getObject(orb, "transportManager");
                        if (transportManager != null) {
                            Thread selector = (Thread) getObject(transportManager, "selector");
                            if (selector != null && loaderRemovable(selector.getContextClassLoader())) {
                                selector.setContextClassLoader(null);
                                logger.log(Level.INFO, "Cleaned orb ref {0}", thread.getName());
                            }
                        }
                    }
                }
            }
        
            private void removeThreadLocal(Object entry, Object threadLocals, Thread thread) {
                ThreadLocal threadLocal = (ThreadLocal) getObject(entry, "referent");
                if (threadLocal != null) {
                    Class clazz = null;
                    try {
                        clazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
                    } catch (ClassNotFoundException ex) {
                        logger.log(Level.WARNING, null, ex);
                    }
                    if (clazz != null) {
                        Method removeMethod = null;
                        Method[] methods = clazz.getDeclaredMethods();
                        if (methods != null) {
                            for (Method method : methods) {
                                if (method.getName().equals("remove")) {
                                    removeMethod = method;
                                    removeMethod.setAccessible(true);
                                    break;
                                }
                            }
                        }
                        if (removeMethod != null) {
                            try {
                                removeMethod.invoke(threadLocals, threadLocal);
                                logger.log(Level.INFO, "Cleaned threadlocal {0}", thread.getName());
                            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                                logger.log(Level.SEVERE, null, ex);
                            }
                        }
        
                    }
        
                }
            }
        
            private void cleanThreadLocal(Thread thread) {
                Object threadLocals = getObject(thread, "threadLocals");
                if (threadLocals != null) {
                    Object table = getObject(threadLocals, "table");
                    if (table != null) {
                        int size = Array.getLength(table);
                        for (int i = 0; i < size; i++) {
                            Object entry = Array.get(table, i);
                            if (entry != null) {
                                Field valueField = getField(entry.getClass(), "value");
                                if (valueField != null) {
                                    try {
                                        Object value = valueField.get(entry);
                                        if (value != null && value instanceof ClassLoader && loaderRemovable((ClassLoader) value)) {
                                            removeThreadLocal(entry, threadLocals, thread);
                                        }
                                    } catch (IllegalArgumentException | IllegalAccessException ex) {
                                        logger.log(Level.WARNING, "Unable to get threadlocal value", ex);
                                    }
        
                                }
                            }
        
                        }
                    }
                }
            }
        
            private void cleanTimers(Thread thread) {
                Object queue = getObject(thread, "queue");
                if (queue != null) {
                    Object queue2 = getObject(queue, "queue");
                    if (queue2 != null) {
                        Object[] taskArray = (Object[]) queue2;
                        for (Object timerTask : taskArray) {
                            if (timerTask != null) {
                                Object timerService = getObject(timerTask, "timerService_");
                                if (timerService != null) {
                                    Object ejbContainerUtil = getObject(timerService, "ejbContainerUtil");
                                    if (ejbContainerUtil != null) {
        
                                        Object orbHelper = getObject(ejbContainerUtil, "orbHelper");
                                        if (orbHelper != null) {
                                            Object protocolManager = getObject(orbHelper, "protocolManager");
                                            if (protocolManager != null) {
                                                Object presentationMgr = getObject(protocolManager, "presentationMgr");
                                                if (presentationMgr != null) {
                                                    //PresentationManagerImpl$1
                                                    Object classToClassData = getObject(presentationMgr, "classToClassData");
                                                    if (classToClassData != null) {
                                                        Object lock = getObject(classToClassData, "lock");
                                                        //WeakHashMapSafeReadLock
                                                        Object map = getObject(classToClassData, "map");
                                                        if (lock != null && lock instanceof ReentrantReadWriteLock && map != null && map instanceof Map) {
                                                            ReentrantReadWriteLock theLock = (ReentrantReadWriteLock) lock;
                                                            Set<Object> toBeRemoved = new HashSet<>();
                                                            synchronized (presentationMgr) {
                                                                synchronized (classToClassData) {
                                                                    theLock.writeLock().lock();
                                                                    try {
                                                                        Map theMap = (Map) map;
                                                                        for (Object key : theMap.keySet()) {
                                                                            if (key != null) {
                                                                                //PresentationManagerImpl$ClassDataImpl
                                                                                Object value = theMap.get(key);
        
                                                                                if (value != null) {
                                                                                    Object cls = getObject(value, "cls");
                                                                                    if (cls != null && cls instanceof Class) {
                                                                                        ClassLoader cl = ((Class) cls).getClassLoader();
                                                                                        if (cl != null) {
                                                                                            if (loaderRemovable(cl) || loaderRemovable(cl.getParent())) {
                                                                                                toBeRemoved.add(key);
                                                                                            }
                                                                                        }
                                                                                    }
        
                                                                                    Object dictionary = getObject(value, "dictionary");
                                                                                    if (dictionary != null && dictionary instanceof Map) {
                                                                                        Iterator it = ((Map) dictionary).values().iterator();
                                                                                        while (it.hasNext()) {
                                                                                            Object o = it.next();
                                                                                            if (o != null  && o instanceof Class) {
                                                                                                ClassLoader cl = ((Class) o).getClassLoader();
                                                                                                if (cl != null) {
                                                                                                    if (loaderRemovable(cl) || loaderRemovable(cl.getParent())) {
                                                                                                        it.remove();
                                                                                                        logger.log(Level.INFO, "Cleaned dictionary: {0}", thread.getName());
                                                                                                    }
                                                                                                }
                                                                                            }
                                                                                        }
        
                                                                                    }
        
                                                                                }
                                                                            }
                                                                        }
        
                                                                    } finally {
                                                                        theLock.writeLock().unlock();
                                                                    }
                                                                }
                                                            }
        
                                                            Method removeMethod = null;
                                                            Method[] methods = presentationMgr.getClass().getDeclaredMethods();
                                                            if (methods != null) {
                                                                for (Method method : methods) {
                                                                    if (method.getName().equals("flushClass")) {
                                                                        removeMethod = method;
                                                                        removeMethod.setAccessible(true);
                                                                        break;
                                                                    }
                                                                }
                                                            }
                                                            if (removeMethod != null) {
                                                                for (Object cls : toBeRemoved) {
                                                                    try {
                                                                        removeMethod.invoke(presentationMgr, cls);
                                                                        logger.log(Level.INFO, "Cleaned classToClassData: {0}", thread.getName());
                                                                    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                                                                        logger.log(Level.SEVERE, null, ex);
                                                                    }
                                                                }
                                                            }
        
                                                        }
        
                                                    }
                                                }
                                            }
                                        }
        
                                        Object timer = getObject(ejbContainerUtil, "_timer");
                                        if (timer != null && timer instanceof Timer) {
                                            int purged = ((Timer) timer).purge();
                                            logger.log(Level.INFO, "Cleaned timer {0}. Purged {1} tasks.", new Object[]{thread.getName(), purged});
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        
        }
        

        There is still a leak in a thread local with the following tree

        this     - value: org.glassfish.javaee.full.deployment.EarClassLoader #6
         <- loader     - class: com.sun.ejb.containers.CMCSingletonContainer, value: org.glassfish.javaee.full.deployment.EarClassLoader #6
          <- container     - class: com.sun.ejb.containers.EJBLocalObjectInvocationHandler, value: com.sun.ejb.containers.CMCSingletonContainer #9
           <- optionalEjbLocalBusinessObjectImpl     - class: com.sun.ejb.containers.SingletonContextImpl, value: com.sun.ejb.containers.EJBLocalObjectInvocationHandler #12
            <- [0]     - class: java.lang.Object[], value: com.sun.ejb.containers.SingletonContextImpl #10
             <- elementData     - class: java.util.ArrayList, value: java.lang.Object[] #96711
              <- beans     - class: com.sun.ejb.containers.ContainerSynchronization, value: java.util.ArrayList #97409
               <- sync     - class: com.sun.ejb.containers.EjbContainerUtilImpl$TxData, value: com.sun.ejb.containers.ContainerSynchronization #2
                <- containerData     - class: com.sun.enterprise.transaction.JavaEETransactionImpl, value: com.sun.ejb.containers.EjbContainerUtilImpl$TxData #2
                 <- clientTx     - class: com.sun.ejb.EjbInvocation, value: com.sun.enterprise.transaction.JavaEETransactionImpl #2
                  <- inv     - class: com.sun.enterprise.security.authorize.HandlerData, value: com.sun.ejb.EjbInvocation #5
                   <- value     - class: java.lang.ThreadLocal$ThreadLocalMap$Entry, value: com.sun.enterprise.security.authorize.HandlerData #6
                    <- [121]     - class: java.lang.ThreadLocal$ThreadLocalMap$Entry[], value: java.lang.ThreadLocal$ThreadLocalMap$Entry #1185
                     <- table     - class: java.lang.ThreadLocal$ThreadLocalMap, value: java.lang.ThreadLocal$ThreadLocalMap$Entry[] #179
                      <- threadLocals (thread object)     - class: java.lang.Thread, value: java.lang.ThreadLocal$ThreadLocalMap #179
        
        Show
        electricsam added a comment - I think the actual issue is that there is a hard reference in the dictionary map in PresentationManagerImpl$ClassDataImpl Here is some cleanup code that I've tried running in the @PreDestroy method of a singleton startup bean to try to clean up classloader leaks. This seems to get rid of many of them. This is just test code. public abstract class ClassLoaderCleaner { private static final Logger logger = Logger.getLogger(ClassLoaderCleaner.class.getName()); private ClassLoader loader = null ; protected void destroy() { try { loader = getClass().getClassLoader(); cleanUp(); } catch (Throwable e) { logger.log(Level.SEVERE, null , e); } } private Thread [] getThreads() { ThreadGroup rootGroup = Thread .currentThread().getThreadGroup(); ThreadGroup parentGroup; while ((parentGroup = rootGroup.getParent()) != null ) { rootGroup = parentGroup; } Thread [] threads = new Thread [rootGroup.activeCount()]; while (rootGroup.enumerate(threads, true ) == threads.length) { threads = new Thread [threads.length * 2]; } return threads; } private boolean loaderRemovable( ClassLoader cl) { if (cl == null ) { return false ; } Object isDoneCalled = getObject(cl, "doneCalled" ); if (cl.getClass().getName().equals(loader.getClass().getName()) && isDoneCalled instanceof Boolean && ( Boolean ) isDoneCalled) { return true ; } return loader == cl; } private Field getField( Class clazz, String fieldName) { Field f = null ; try { f = clazz.getDeclaredField(fieldName); } catch (NoSuchFieldException ex) { } catch (SecurityException ex) { logger.log(Level.WARNING, "Unable to get field " + fieldName + " on " + clazz.getName(), ex); } if (f == null ) { Class parent = clazz.getSuperclass(); if (parent != null ) { f = getField(parent, fieldName); } } if (f != null ) { f.setAccessible( true ); } return f; } private Object getObject( Object instance, String fieldName) { Class clazz = instance.getClass(); Field f = getField(clazz, fieldName); if (f != null ) { try { return f.get(instance); } catch (IllegalArgumentException | IllegalAccessException ex) { logger.log(Level.WARNING, "Unable to get " + fieldName + " on " + clazz.getName(), ex); } } return null ; } private void cleanUp() { Thread [] threads = getThreads(); for ( Thread thread : threads) { if (thread != null ) { cleanContextClassLoader(thread); cleanOrb(thread); cleanThreadLocal(thread); cleanTimers(thread); } } } private void cleanContextClassLoader( Thread thread) { if (loaderRemovable(thread.getContextClassLoader())) { thread.setContextClassLoader( null ); logger.log(Level.INFO, "Cleaned context classloader {0}" , thread.getName()); } } private void cleanOrb( Thread thread) { Object currentWork = getObject(thread, "currentWork" ); if (currentWork != null ) { Object orb = getObject(currentWork, "orb" ); if (orb != null ) { Object transportManager = getObject(orb, "transportManager" ); if (transportManager != null ) { Thread selector = ( Thread ) getObject(transportManager, "selector" ); if (selector != null && loaderRemovable(selector.getContextClassLoader())) { selector.setContextClassLoader( null ); logger.log(Level.INFO, "Cleaned orb ref {0}" , thread.getName()); } } } } } private void removeThreadLocal( Object entry, Object threadLocals, Thread thread) { ThreadLocal threadLocal = (ThreadLocal) getObject(entry, "referent" ); if (threadLocal != null ) { Class clazz = null ; try { clazz = Class .forName( "java.lang.ThreadLocal$ThreadLocalMap" ); } catch (ClassNotFoundException ex) { logger.log(Level.WARNING, null , ex); } if (clazz != null ) { Method removeMethod = null ; Method[] methods = clazz.getDeclaredMethods(); if (methods != null ) { for (Method method : methods) { if (method.getName().equals( "remove" )) { removeMethod = method; removeMethod.setAccessible( true ); break ; } } } if (removeMethod != null ) { try { removeMethod.invoke(threadLocals, threadLocal); logger.log(Level.INFO, "Cleaned threadlocal {0}" , thread.getName()); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { logger.log(Level.SEVERE, null , ex); } } } } } private void cleanThreadLocal( Thread thread) { Object threadLocals = getObject(thread, "threadLocals" ); if (threadLocals != null ) { Object table = getObject(threadLocals, "table" ); if (table != null ) { int size = Array.getLength(table); for ( int i = 0; i < size; i++) { Object entry = Array.get(table, i); if (entry != null ) { Field valueField = getField(entry.getClass(), "value" ); if (valueField != null ) { try { Object value = valueField.get(entry); if (value != null && value instanceof ClassLoader && loaderRemovable(( ClassLoader ) value)) { removeThreadLocal(entry, threadLocals, thread); } } catch (IllegalArgumentException | IllegalAccessException ex) { logger.log(Level.WARNING, "Unable to get threadlocal value" , ex); } } } } } } } private void cleanTimers( Thread thread) { Object queue = getObject(thread, "queue" ); if (queue != null ) { Object queue2 = getObject(queue, "queue" ); if (queue2 != null ) { Object [] taskArray = ( Object []) queue2; for ( Object timerTask : taskArray) { if (timerTask != null ) { Object timerService = getObject(timerTask, "timerService_" ); if (timerService != null ) { Object ejbContainerUtil = getObject(timerService, "ejbContainerUtil" ); if (ejbContainerUtil != null ) { Object orbHelper = getObject(ejbContainerUtil, "orbHelper" ); if (orbHelper != null ) { Object protocolManager = getObject(orbHelper, "protocolManager" ); if (protocolManager != null ) { Object presentationMgr = getObject(protocolManager, "presentationMgr" ); if (presentationMgr != null ) { //PresentationManagerImpl$1 Object classToClassData = getObject(presentationMgr, "classToClassData" ); if (classToClassData != null ) { Object lock = getObject(classToClassData, "lock" ); //WeakHashMapSafeReadLock Object map = getObject(classToClassData, "map" ); if (lock != null && lock instanceof ReentrantReadWriteLock && map != null && map instanceof Map) { ReentrantReadWriteLock theLock = (ReentrantReadWriteLock) lock; Set< Object > toBeRemoved = new HashSet<>(); synchronized (presentationMgr) { synchronized (classToClassData) { theLock.writeLock().lock(); try { Map theMap = (Map) map; for ( Object key : theMap.keySet()) { if (key != null ) { //PresentationManagerImpl$ClassDataImpl Object value = theMap.get(key); if (value != null ) { Object cls = getObject(value, "cls" ); if (cls != null && cls instanceof Class ) { ClassLoader cl = (( Class ) cls).getClassLoader(); if (cl != null ) { if (loaderRemovable(cl) || loaderRemovable(cl.getParent())) { toBeRemoved.add(key); } } } Object dictionary = getObject(value, "dictionary" ); if (dictionary != null && dictionary instanceof Map) { Iterator it = ((Map) dictionary).values().iterator(); while (it.hasNext()) { Object o = it.next(); if (o != null && o instanceof Class ) { ClassLoader cl = (( Class ) o).getClassLoader(); if (cl != null ) { if (loaderRemovable(cl) || loaderRemovable(cl.getParent())) { it.remove(); logger.log(Level.INFO, "Cleaned dictionary: {0}" , thread.getName()); } } } } } } } } } finally { theLock.writeLock().unlock(); } } } Method removeMethod = null ; Method[] methods = presentationMgr.getClass().getDeclaredMethods(); if (methods != null ) { for (Method method : methods) { if (method.getName().equals( "flushClass" )) { removeMethod = method; removeMethod.setAccessible( true ); break ; } } } if (removeMethod != null ) { for ( Object cls : toBeRemoved) { try { removeMethod.invoke(presentationMgr, cls); logger.log(Level.INFO, "Cleaned classToClassData: {0}" , thread.getName()); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { logger.log(Level.SEVERE, null , ex); } } } } } } } } Object timer = getObject(ejbContainerUtil, "_timer" ); if (timer != null && timer instanceof Timer) { int purged = ((Timer) timer).purge(); logger.log(Level.INFO, "Cleaned timer {0}. Purged {1} tasks." , new Object []{thread.getName(), purged}); } } } } } } } } } There is still a leak in a thread local with the following tree this - value: org.glassfish.javaee.full.deployment.EarClassLoader #6 <- loader - class: com.sun.ejb.containers.CMCSingletonContainer, value: org.glassfish.javaee.full.deployment.EarClassLoader #6 <- container - class: com.sun.ejb.containers.EJBLocalObjectInvocationHandler, value: com.sun.ejb.containers.CMCSingletonContainer #9 <- optionalEjbLocalBusinessObjectImpl - class: com.sun.ejb.containers.SingletonContextImpl, value: com.sun.ejb.containers.EJBLocalObjectInvocationHandler #12 <- [0] - class: java.lang.Object[], value: com.sun.ejb.containers.SingletonContextImpl #10 <- elementData - class: java.util.ArrayList, value: java.lang.Object[] #96711 <- beans - class: com.sun.ejb.containers.ContainerSynchronization, value: java.util.ArrayList #97409 <- sync - class: com.sun.ejb.containers.EjbContainerUtilImpl$TxData, value: com.sun.ejb.containers.ContainerSynchronization #2 <- containerData - class: com.sun.enterprise.transaction.JavaEETransactionImpl, value: com.sun.ejb.containers.EjbContainerUtilImpl$TxData #2 <- clientTx - class: com.sun.ejb.EjbInvocation, value: com.sun.enterprise.transaction.JavaEETransactionImpl #2 <- inv - class: com.sun.enterprise.security.authorize.HandlerData, value: com.sun.ejb.EjbInvocation #5 <- value - class: java.lang.ThreadLocal$ThreadLocalMap$Entry, value: com.sun.enterprise.security.authorize.HandlerData #6 <- [121] - class: java.lang.ThreadLocal$ThreadLocalMap$Entry[], value: java.lang.ThreadLocal$ThreadLocalMap$Entry #1185 <- table - class: java.lang.ThreadLocal$ThreadLocalMap, value: java.lang.ThreadLocal$ThreadLocalMap$Entry[] #179 <- threadLocals (thread object) - class: java.lang.Thread, value: java.lang.ThreadLocal$ThreadLocalMap #179

          People

          • Assignee:
            Srini
            Reporter:
            electricsam
          • Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

            • Created:
              Updated: