How to crash the java virtual machine with a race condition

March 03, 2017

This is a how-to guide for crashing the java virtual machine. It gives you an introduction to race conditions and shows you what errors can happen if your code contains such bugs.

Create a race condition

Let us start with the following method:
public class DataRaceTest implements Runnable {
   private Type[] instance;
   @Override
   public void run() {
     if (instance == null) {
	   Type[] ts = new Type[1];
	   ts[0] = Object.class;
	   instance = ts;
	  }
	  instance[0].getTypeName();
   }
}

If this method is executed by multiple threads it leads to a race condition. More specifically it leads to a data race. Data races are defined in the java memory model as an access to a shared field without correct synchronization. According to the java memory model data races lead to platform dependent undefined behavior:

Without correct synchronization, very strange, confusing and counterintuitive behaviors are possible.
If the code is executed in the given order everything is o.k. But if some component reorders the statements, the array might not be completely initialized. One such component is the cache system of the CPU. Intel Processors, for example, have a cache system with a strong memory guarantees. The cache system makes sure that the values written by the cores are almost always seen in the same order as they have been written by the other cores. But ARM compatible processors like in smartphones or the Raspberry Pi do not give this guarantee.

Create a Nullpointer Exception

To see what is happening, I execute the method multiple times by multiple threads To do this I use a tool to reproduce race conditions, called stress test. I run it with the following command line options:
java -cp stress-test-0.0.1-SNAPSHOT-jar-with-dependencies.jar com.vmlens.StressTest 
   -i 5 -w 16 com.vmlens.stressTest.examples.dataRace.DataRaceTestSetup  

using the following test setup class:

public class DataRaceTestSetup implements TestSetup  {
   @Override
   public Runnable createTest() {
		return  new DataRaceTest();
   }
}

This runs the method run of DataRaceTest for 5 iterations. Each iteration consists of 16.000 tests run by 16 threads.

If I run this on my intel i5 workstation, I could not create an exception. If I run this on my Raspberry Pi, I see the following Nullpointer Exception for every 2000 tests:

java.lang.NullPointerException
        at com.vmlens.stressTest.examples.dataRace.DataRaceTest.run(DataRaceTest.java:78)
        at com.vmlens.stressTest.internal.TestCall.call(TestCall.java:36)
        at com.vmlens.stressTest.internal.TestCall.call(TestCall.java:7)
        at com.vmlens.stressTest.internal.WorkerThread.run(WorkerThread.java:22)

Crash the java virtual machine

Now let us add some native call. Let us clone the array:
@Override
	public void run() {
			if (instance == null) {
				Type[] ts = new Type[1];
				ts[0] = Object.class;
				instance = ts;
			}
		Type[] clonedInstance = instance.clone();
		clonedInstance[0].getTypeName();
	}

This time I run the test till I see at least one error. This is done by using the -e option:

java -cp stress-test-0.0.1-SNAPSHOT-jar-with-dependencies.jar com.vmlens.StressTest
   -e 1 -w 16 com.vmlens.stressTest.examples.dataRace.DataRaceTestSetup 

If I run this code on a Raspberry Pi, I see a java virtual machine crash after some time. It takes between half an hour and a day:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x741ab340, pid=26364, tid=1682633824
#
# JRE version: Java(TM) SE Runtime Environment (8.0_65-b17) (build 1.8.0_65-b17)
# Java VM: Java HotSpot(TM) Client VM (25.65-b01 mixed mode linux-arm )
# Problematic frame:
# J 38 C1 com.vmlens.stressTest.internal.TestSetupCall.call()Lcom/vmlens/stressTest/internal/Result; (45 bytes) @ 0x741ab340 [0x741ab250+0xf0]

Conclusion

Data races are hard to reproduce. You need different types of hardware. And even then it takes time till an error occurs.

That is the reason I developed vmlens, a tool to detect data races in the execution trace of an application.

testing multi-threaded applications on the JVM made easy

LEARN MORE

© 2020 vmlens Legal Notice Privacy Policy