FULL PRODUCT VERSION :
java version "1.6.0_12"
Java(TM) SE Runtime Environment (build 1.6.0_12-b04)
Java HotSpot(TM) Server VM (build 11.2-b01, mixed mode)
The bug has been observed with a few versions of Java 6 and 7.
ADDITIONAL OS VERSION INFORMATION :
Linux samoa 2.6.31-23-generic-pae #75-Ubuntu SMP Fri Mar 18 19:14:10 UTC 2011 i686 GNU/Linux
The bug has been observed on a couple versions of Windows as well.
A DESCRIPTION OF THE PROBLEM :
Loading ICC color profiles from multiple threads sometimes triggers a null pointer exception inside the JRE's ICC_Profile class.
This bug report is similar to (but not a duplicate of) this one:
JDK-6986863 : ProfileDeferralMgr throwing ConcurrentModificationException
Ultimately, ProfileDeferralMgr has non-final static fields that are not synchronized and one of them is a container that's not using a thread safe class. All the methods on ProfileDeferralMgr should probably be synchronized. The call to ProfileDeferralMgr.unregisterDeferral() should be removed from ICC_Profile.finalize(), and any other finalizers, since a profile won't get gc'ed if it's referenced from the static list in ProfileDeferralMgr.
Also, JDK-6793818 seems to be the reason that all of the standard profiles are lazy loaded with Java 7, where only the sRGB profile was lazy loaded with Java 6. The chance of hitting this bug with 7 is probably greater than with 6.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Load the JRE's sRGB color profile, then on two or more threads load any non-JRE color profile. There is a race condition between the threads when they execute the lazy loading of the standard profiles inside ICC_Profile and ProfileDeferralMgr.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Calls to ICC_Profile.getInstance() should be multi-thread safe, especially when loading profiles from a byte array or stream (as opposed to loading the standard JRE profiles that are cached inside ICC_Profile).
ACTUAL -
Occassional null pointer exceptions in threads that load ICC profiles.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "Thread-0" java.lang.NullPointerException
at java.awt.color.ICC_Profile.activateDeferredProfile(ICC_Profile.java:1052)
at java.awt.color.ICC_Profile$1.activate(ICC_Profile.java:723)
at sun.awt.color.ProfileDeferralMgr.activateProfiles(ProfileDeferralMgr.java:75)
at java.awt.color.ICC_Profile.getInstance(ICC_Profile.java:756)
at java.awt.color.ICC_Profile.getInstance(ICC_Profile.java:976)
at java.awt.color.ICC_Profile.getInstance(ICC_Profile.java:941)
at com.blah.ProfileReader$1.run(ProfileReader2.java:38)
REPRODUCIBILITY :
This bug can be reproduced occasionally.
---------- BEGIN SOURCE ----------
package com.blah;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;
// This tester fails about 1 in 4 runs.
public class ProfileReader {
public static void main(String[] args) throws InterruptedException {
// This puts a deferred load in the queue
ICC_Profile.getInstance(ColorSpace.CS_sRGB);
List<Thread> threads = new ArrayList<Thread>();
final AtomicInteger successCount = new AtomicInteger();
int numThreads = 100;
final CyclicBarrier barrier = new CyclicBarrier(numThreads);
for (int i = 0; i < numThreads; i++) {
Thread thread = new Thread() {
@Override
public void run() {
try {
barrier.await();
// This loads some other profile, triggering the delayed load of sRGB
ICC_Profile.getInstance("/usr/lib/jdk1.6.0_12/jre/lib/cmm/GRAY.pf");
successCount.incrementAndGet();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
};
thread.start();
threads.add(thread);
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("Profile load failures = " + (numThreads - successCount.get()));
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
In application code synchronize all calls to ICC_Profile.getInstance(). This isn't a complete solution, since some calls to ICC_Profile.getInstance() come in from elsewhere in the JRE, as shown in bug "JDK-6986863".