United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-8028054 : com.sun.beans.finder.MethodFinder has unsynchronized access to a static Map

Details
Type:
Bug
Submit Date:
2013-11-04
Status:
Resolved
Updated Date:
2013-12-10
Project Name:
JDK
Resolved Date:
2013-11-26
Component:
client-libs
OS:
linux_ubuntu
Sub-Component:
java.beans
CPU:
Priority:
P3
Resolution:
Fixed
Affected Versions:
7u45,8
Fixed Versions:

Related Reports
Backport:
Duplicate:
Relates:
Relates:

Sub Tasks

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.
                                    

Comments
webrev: http://cr.openjdk.java.net/~malenkov/8028054.8.0/
review: http://mail.openjdk.java.net/pipermail/swing-dev/2013-November/003139.html
issue impact: it is possible to hang a thread of another application under the same VM
risk level: low, the fix does not affect other areas
testing: 2 automatic regression tests are added
                                     
2013-11-22
Release team: Approved for fixing
                                     
2013-11-25
URL:   http://hg.openjdk.java.net/jdk8/awt/jdk/rev/6829d28b3da5
User:  malenkov
Date:  2013-11-26 09:32:29 +0000

                                     
2013-11-26
is it affected jdk8?
if Yes, is it critical or defer case?
                                     
2013-11-08
URL:   http://hg.openjdk.java.net/jdk8/jdk8/jdk/rev/6829d28b3da5
User:  lana
Date:  2013-12-03 19:10:36 +0000

                                     
2013-12-03
This is regression since 7b46. See 4864117.
Need to rewrite the WeakCache class.
                                     
2013-11-08
Yes, it is affected jdk8.
I think it would be better to fix.
                                     
2013-11-08
http://cr.openjdk.java.net/~malenkov/8028054.8.0/
                                     
2013-11-14
The 8023310 issue shows how is slow a simple synchronization.
I've created the Cache class that is similar to WeakIdentityMap.
                                     
2013-11-14
SQE: OK to push in jdk8
                                     
2013-11-22
Please add recommendations on testing besides running two new regression tests.
                                     
2013-12-06
The new tests covered pretty all scenarios that we could imagine,
to be on the safe side please run the regression tests from the jdk/test/java/beans folder
(we also checked them - no problems found)
                                     
2013-12-10
SQE is ok to take the fix in 7u60.
                                     
2013-12-10



Hardware and Software, Engineered to Work Together