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

com.sun.beans.finder.MethodFinder has unsynchronized access to a static Map

    Details

    • Subcomponent:
    • Resolved In Build:
      b119
    • OS:
      linux_ubuntu

      Backports

        Description

        FULL PRODUCT VERSION :
        java version "1.7.0_40"
        Java(TM) SE Runtime Environment (build 1.7.0_40-b43)
        Java HotSpot(TM) 64-Bit Server VM (build 24.0-b56, mixed mode)


        ADDITIONAL OS VERSION INFORMATION :
        3.8.0-31-generic #46-Ubuntu SMP Tue Sep 10 20:03:44 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

        A DESCRIPTION OF THE PROBLEM :
        com.sun.beans.finder.MethodFinder has a private static instance CACHE, of type com.sun.beans.WeakCache, which is a thin wrapper around java.util.WeakHashMap. As a result, under concurrent usage, it's possible for the underlying map to contain a circular reference in one of its bucket's linked lists. If a thread subsequently attempts to find a record in that bucket which does not yet exist, it will get stuck in the while loop of WeakHashMap's get method; stack traces will continually show the thread as being at line 470 (e = e.next).

        While this is in a private com.sun package, it can be exposed to clients only consuming public APIs. As an example, consider the following stack trace:

                at java.util.WeakHashMap.get(WeakHashMap.java:470)
                at com.sun.beans.WeakCache.get(WeakCache.java:55)
                at com.sun.beans.finder.MethodFinder.findMethod(MethodFinder.java:68)
                at java.beans.Statement.getMethod(Statement.java:357)
                at java.beans.Statement.invokeInternal(Statement.java:287)
                at java.beans.Statement.access$000(Statement.java:58)
                at java.beans.Statement$2.run(Statement.java:185)
                at java.security.AccessController.doPrivileged(Native Method)
                at java.beans.Statement.invoke(Statement.java:182)
                at java.beans.Expression.getValue(Expression.java:153)
                ....

        One place this bug is showing up in the wild is https://issues.apache.org/jira/browse/HIVE-4574.

        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        Given that this is a concurrency bug, it cannot be reproduced consistently, but the included code triggers it more often than not for me. Compile the code, then run it. Note that rt.jar needs to be on the compile class path.

        javac -classpath /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar MethodFinderBug.java
        java MethodFinderBug

        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        The program will run for awhile, but eventually terminate, having printed "done!".
        ACTUAL -
        Frequently, the program does not terminate, but ends up printing the following every two seconds:

        progress seen in 0 out of 50 threads
        50 of 50 threads are in WeakHashMap code


        REPRODUCIBILITY :
        This bug can be reproduced often.

        ---------- BEGIN SOURCE ----------

        import java.io.IOException;
        import java.lang.reflect.Method;
        import java.lang.reflect.Modifier;
        import java.util.ArrayList;
        import java.util.Enumeration;
        import java.util.List;
        import java.util.WeakHashMap;
        import java.util.concurrent.atomic.AtomicLongArray;
        import java.util.jar.JarEntry;
        import java.util.jar.JarFile;
        import java.util.regex.Matcher;
        import java.util.regex.Pattern;

        import com.sun.beans.finder.MethodFinder;


        public class MethodFinderBug {
          public static void main(String[] args) throws Exception {
            final List<Method> methods = getMethods(grabClasses(5000));

            Thread[] threads = new Thread[50];
            final AtomicLongArray timestamps = new AtomicLongArray(threads.length);

            for (int i = 0; i < threads.length; i++) {
              final int threadNumber = i;
              threads[i] = new Thread() {
                @Override
                public void run() {
                  for (Method method: methods) {
                    try {
                      MethodFinder.findMethod(method.getDeclaringClass(), method.getName(), method.getParameterTypes());
                      timestamps.set(threadNumber, System.currentTimeMillis());
                    }
                    catch (NoSuchMethodException e) {
                    }
                  }
                  timestamps.set(threadNumber, 0); // mark as complete
                  System.out.println("Thread " + threadNumber + " complete");
                }
              };
            }
            for (int i = 0; i < timestamps.length(); i++) {
              timestamps.set(i, System.currentTimeMillis());
            }

            for (Thread thread: threads) {
              thread.start();
            }

            final int checkInterval = 2000; // 2 seconds
            while (true) {
              boolean someThreadsNotFinished = false;
              int progressingThreadCount = 0;
              for (int i = 0; i < timestamps.length(); i++) {
                long threadTime = timestamps.get(i);
                if (threadTime > 0) {
                  someThreadsNotFinished = true;
                  if (System.currentTimeMillis() - threadTime <= checkInterval) {
                    progressingThreadCount++;
                  }
                }
              }
              if (someThreadsNotFinished) {
                // look for threads that may be stuck in a loop inside WeakHashMap
                int hashMapLoopCount = 0;
                int activeThreadCount = 0;
                for (Thread thread: threads) {
                  if (thread.isAlive()) {
                    activeThreadCount++;
                    StackTraceElement[] stackTrace = thread.getStackTrace();
                    if (stackTrace != null
                        && stackTrace.length > 0
                        && WeakHashMap.class.getName().equals(stackTrace[0].getClassName())) {
                      hashMapLoopCount++;
                    }
                  }
                }
                System.out.println("progress seen in " + progressingThreadCount + " out of " + activeThreadCount + " threads");
                System.out.println(hashMapLoopCount + " of " + activeThreadCount + " threads are in WeakHashMap code");
                Thread.sleep(2000);
              }
              else {
                break;
              }
            }
            System.out.println("done!");
          }

          private static List<Method> getMethods(List<Class<?>> classes) {
            List<Method> methods = new ArrayList<>();
            for (Class<?> clazz: classes) {
              for (Method method: clazz.getMethods()) {
                int modifiers = method.getModifiers();
                if (Modifier.isPublic(modifiers) && !Modifier.isAbstract(modifiers)) {
                  methods.add(method);
                }
              }
            }
            return methods;
          }

          private static List<Class<?>> grabClasses(int classCount) throws IOException {
            Pattern pattern = Pattern.compile("jar:file:(.*)!.*");
            Matcher matcher = pattern.matcher(
              ClassLoader.getSystemClassLoader().getResource("java/lang/Object.class").toString());
            matcher.matches();
            String jarFileName = matcher.group(1);

            final List<Class<?>> classes = new ArrayList<>();
            try(JarFile rtJar = new JarFile(jarFileName)) {
              int foundClasses = 0;
              for (Enumeration<JarEntry> entries = rtJar.entries(); entries.hasMoreElements(); ) {
                JarEntry jarEntry = entries.nextElement();
                String name = jarEntry.getName();

                if (name.startsWith("java") && name.endsWith(".class")) {
                  try {
                    String className = name.substring(0, name.indexOf(".")).replace('/', '.');
                    classes.add(Class.forName(className));
                    foundClasses++;
                    if (foundClasses > classCount) {
                      break;
                    }
                  }
                  catch (ClassNotFoundException e) {
                    e.printStackTrace();
                  }
                }
              }
            }
            return classes;
          }
        }

        ---------- END SOURCE ----------

        CUSTOMER SUBMITTED WORKAROUND :
        There is no workaround that I am aware of, short of avoiding use of the java.beans API entirely.

          Attachments

            Issue Links

              Activity

                People

                • Assignee:
                  malenkov Sergey Malenkov (Inactive)
                  Reporter:
                  malenkov Sergey Malenkov (Inactive)
                • Votes:
                  0 Vote for this issue
                  Watchers:
                  11 Start watching this issue

                  Dates

                  • Created:
                    Updated:
                    Resolved: