-
Bug
-
Resolution: Fixed
-
P3
-
7u45, 8
-
b119
-
linux_ubuntu
Issue | Fix Version | Assignee | Priority | Status | Resolution | Resolved In Build |
---|---|---|---|---|---|---|
JDK-8034686 | 7u65 | Anton Nashatyrev | P3 | Resolved | Fixed | b01 |
JDK-8029531 | 7u60 | Anton Nashatyrev | P3 | Closed | Fixed | b02 |
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.
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.
- backported by
-
JDK-8034686 com.sun.beans.finder.MethodFinder has unsynchronized access to a static Map
-
- Resolved
-
-
JDK-8029531 com.sun.beans.finder.MethodFinder has unsynchronized access to a static Map
-
- Closed
-
- duplicates
-
JDK-8028277 Deadlock in java.beans.XMLDecoder
-
- Closed
-
- relates to
-
JDK-8023310 Thread contention in the method Beans.IsDesignTime()
-
- Resolved
-
-
JDK-4864117 RFE: Make XMLDecoder API more reusable
-
- Closed
-