Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-6992761

Memory leak in Level class via the "known" data variable

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Duplicate
    • Icon: P3 P3
    • None
    • 7
    • core-libs

      A DESCRIPTION OF THE REQUEST :
      According to Java Docs " It is possible for third parties to define additional logging levels by subclassing Level." An application creates its own LogLevel and instantiates it. When the application is undeployed the application classloader is pinned in memory via the custom LogLevel.
      The Level class has a static ArrayList "known" to which each LogLevel is added. There is no way to clear out the application specific levels when an application is undeployed thus causing a memory leak.

      JUSTIFICATION :
      In developer mode when applications are deployed and undeployed constantly it is easy to get an OutOfMemory error as the classloaders eat up the perm space.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Custom log levels should be removed from the static arraylist whenever the application is undeployed.
      ACTUAL -
      Currently an instantiated Level remains in the "known" ArrayList for the lifetime of the JVM, irrespective of the application lifecycle.

      ---------- BEGIN SOURCE ----------
      package level.testcase;

      import java.io.IOException;
      import java.io.InputStream;
      import java.lang.ref.Reference;
      import java.lang.ref.WeakReference;
      import java.lang.reflect.Field;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.logging.Level;


      class LogLevelLeakDetector {
          public static void main(String [] args) {
              LogLevelLeaker leaker = new LogLevelLeaker();
              leaker.showLoaderLeaking();
          }

          static class LogLevelLeaker {

              public LogLevelLeaker() {

              }

              public void showLoaderLeaking() {
                  TestClassLoader testClassLoader;

                  //create the custom loader
                  testClassLoader = new TestClassLoader();
                  //create the custom level and set it on the logger
                  Level testLevel = getTestLevel(testClassLoader);

                  WeakReference r = new WeakReference(testClassLoader);
                  WeakReference t = new WeakReference(testLevel);
                  testClassLoader = null;


                  System.out.println("testLevel collected?: " + collectible(t));
                  System.out.println("testClassLoader collected?: " + collectible(r));

                  //if no leak is to be asserted use reflection to clear the custom log level from the
                  //Level class cache.
                  System.out.println("Explicitly clearing the custom level for the Level class cache");
                  clearCustomLogLevelFromCache(testLevel);
                  testLevel = null;

                  System.out.println("testLevel collected?: " + collectible(t));
                  System.out.println("testClassLoader collected?: " + collectible(r));

              }

              // Try as hard as possible to collect a reference, even if soft.
              // Copied from org.netbeans.junit.NbTestCase.assertGC.
              private static boolean collectible(Reference ref) {
                  List alloc = new ArrayList();
                  int size = 100000;
                  for (int i = 0; i < 50; i++) {
                      if (ref.get() == null) {
                          return true;
                      }
                      System.gc();
                      System.runFinalization();
                      try {
                          alloc.add(new byte[size]);
                          size = (int) (((double) size) * 1.3);
                      } catch (OutOfMemoryError error) {
                          size = size / 2;
                      }
                      try {
                          if (i % 3 == 0) {
                              Thread.sleep(321);
                          }
                      } catch (InterruptedException t) {
                      }
                  }
                  return false;
              }

              private void clearCustomLogLevelFromCache(Level levelToReset) {
                  List levelsList = getCachedLogLevels();
                  Object[] levelsListCopy = levelsList.toArray();
                  synchronized (Level.class) {
                      for (int i = 0; i < levelsListCopy.length; i++) {
                          Level level = (Level) levelsListCopy[i];

                          if (level == levelToReset) {
                              levelsList.remove(i);
                          }
                      }
                  }
                  levelsList = null;
              }

              private Level getTestLevel(TestClassLoader testClassLoader) {
                  String className = "level/testcase/LogLevelLeakDetector$MockLogLevel.class";
                  byte[] classData = null;
                  try {
                      classData = read(getClass().getClassLoader().getResourceAsStream(className));
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
                  Class myClass = testClassLoader.defineClass("level.testcase.LogLevelLeakDetector$MockLogLevel", classData);
                  Level myLevel = null;
                  try {
                      myLevel = (Level) myClass.newInstance();
                  } catch (InstantiationException e) {
                      e.printStackTrace();
                  } catch (IllegalAccessException e) {
                      e.printStackTrace();
                  }
                  return myLevel;
              }

              /**
               * Helper method to get all the Log Levels cached in the Level class
               *
               * @return list of cached levels
               */
              private List getCachedLogLevels() {
                  ArrayList levelsList;

                  try {
                      Class cls = Level.class;
                      Field reqdFld = cls.getDeclaredField("known");
                      reqdFld.setAccessible(true);

                      synchronized (cls) {
                          levelsList = (ArrayList) reqdFld.get(null);
                      }
                  } catch (NoSuchFieldException e) {
                      //should only get here if the internal data structures in the Level class change.
                      return null;
                  } catch (IllegalAccessException e) {
                      return null;
                  }

                  return levelsList;
              }

              /**
               * Get the contents of a stream as a byte array.
               *
               * @param in The stream.
               * @return The array, trimmed to the correct size. The stream is closed.
               */
              private byte[] read(InputStream in) throws IOException {
                  byte[] result;
                  try {
                      int readPosition = 0;
                      int available = in.available();

                      result = new byte[available <= 0 ? 1024 : available];
                      while (true) {
                          int bytesRead = in.read(result, readPosition, result.length - readPosition);
                          if (bytesRead < 0) {
                              break;
                          }
                          readPosition += bytesRead;
                          if (readPosition == result.length) {
                              int peek = in.read();
                              if (peek < 0) {
                                  break;
                              }
                              byte[] temp = new byte[result.length * 2];
                              System.arraycopy(result, 0, temp, 0, readPosition);
                              result = temp;
                              result[readPosition++] = (byte) peek;
                          }
                      }
                      if (readPosition < result.length) {
                          byte[] temp = new byte[readPosition];
                          System.arraycopy(result, 0, temp, 0, readPosition);
                          result = temp;
                      }
                  } finally {
                      in.close();
                  }
                  return result;
              }
          }

          public static class TestClassLoader extends ClassLoader {
              public TestClassLoader() {
                  super();
              }

              public Class defineClass(String name, byte[] classData) {
                  return super.defineClass(name, classData, 0, classData.length);
              }
          }


          public static class MockLogLevel extends Level {

              public MockLogLevel() {
                  super("mockloglevel", Level.WARNING.intValue() - 2);
              }
          }
      }
      ---------- END SOURCE ----------

            dcubed Daniel Daugherty
            ndcosta Nelson Dcosta (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: