March 20, 2017
java.lang.reflect.TypeVariable getBounds is not thread safe. Calling it from multiple threads might even crash your JVM
The following method shows you the use of getBounds(). The method getBounds is used to get the upper bound(s) of a generic type:
public void testGetBounds() { Class cl = GenericInterface.class; TypeVariable typeVariable = cl.getTypeParameters()[0]; typeVariable.getBounds()[0].getTypeName(); }
To make the examples and tests shorter, I do not iterate over the returned array but simply use the first element. Here is the generic interface used in the example:
package com.vmlens.stressTest.util; public interface GenericInterface> { }
If you call testGetBounds from multiple threads, calling getBounds leads to a race condition.
package sun.reflect.generics.reflectiveObjects; // import statements omitted public class TypeVariableImpl extends LazyReflectiveObjectGenerator implements TypeVariable { // upper bounds - evaluated lazily private Type[] bounds; public Type[] getBounds() { // lazily initialize bounds if necessary if (bounds == null) { FieldTypeSignature[] fts = getBoundASTs(); // get AST // allocate result array; note that // keeping ts and bounds separate helps with threads Type[] ts = new Type[fts.length]; // iterate over bound trees, reifying each in turn for ( int j = 0; j < fts.length; j++) { Reifier r = getReifier(); fts[j].accept(r); ts[j] = r.getResult(); } // cache result bounds = ts; // could throw away bound ASTs here; thread safety? } return bounds.clone(); // return cached bounds } // other fields and methods omitted }
In line 15 and 30 the field bounds
is read and in line 27 it is written. If the code is executed in the given order everything is o.k. But if some component reorders the statements, the array is not completely initialized. In pseudo code the method getBounds looks like this:
if instance variable bounds is null { set local variable ts to new Array initialize the array set instance variable bounds to the local variable ts } return instance variable bounds.clone
If the statements get reordered another thread sees an uninitialised array:
Thread A | set local variable ts to new Array | |
Thread A | set instance variable bounds to the local variable ts | |
Thread B | if instance variable bounds is null | |
Thread B | Thread B return instance variable bounds.clone // the array is not yet completely initialized |
One such component is the cache system of the CPU. ARM compatible processors like in smartphones or the Raspberry Pi reorder reads and writes to improve performance, leading to a scenario as described above.
The Java Concurrency Stress tests (jcstress) is an experimental harness and a suite of tests to aid the research in the correctness of concurrency support in the JVM, class libraries, and hardware.
I use the following test class:
@JCStressTest @Outcome(id = "0, 0", expect = Expect.ACCEPTABLE, desc = "Default outcome.") @State public class TypeVariableGetBounds { private final Class cl; public TypeVariableGetBounds() { try { cl = (new StressTestClassLoader(TypeVariableGetBounds.class.getClassLoader())) .loadClass("com.vmlens.stressTest.util.GenericInterface"); } catch (Exception e) { throw new RuntimeException("Test setup incorrect", e); } } public void callContainsDataRace() { TypeVariable typeVariable = cl.getTypeParameters()[0]; typeVariable.getBounds()[0].getTypeName(); } @Actor public void actor1(IntResult2 r) { callContainsDataRace(); } @Actor public void actor2(IntResult2 r) { callContainsDataRace(); } }
Jcstress runs this test multiple times always calling the method actor1 and actor2 from separate threads. To separate the tests, I use a special classloader, which always reloads a class.
When I call this test on a raspberry pi using the test mode tough or stress, I see a crash of the JVM:
# # A fatal error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0x7665b4c0, pid=16112, tid=1680598112 # # 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: # V [libjvm.so+0x27a4c0]The JVM error log shows the following:
Stack: [0x6426f000,0x642bf000], sp=0x642bd8b8, free space=314k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) V [libjvm.so+0x27a4c0] Java frames: (J=compiled Java code, j=interpreted, Vv=VM code) J 1303 java.lang.Object.clone()Ljava/lang/Object; (0 bytes) @ 0x742ba71c [0x742ba6e0+0x3c] J 1313 C1 sun.reflect.generics.repository.GenericDeclRepository.getTypeParameters()[Ljava/lang/reflect/TypeVariable; (80 bytes) @ 0x742b8210 [0x742b7f40+0x2d0] J 1229 C1 com.vmlens.stressTest.tests.TypeVariableGetBounds_jcstress.actor1()Ljava/lang/Void; (109 bytes) @ 0x742a6cb8 [0x742a6bf0+0xc8] j com.vmlens.stressTest.tests.TypeVariableGetBounds_jcstress$$Lambda$6.call()Ljava/lang/Object;+4 j java.util.concurrent.FutureTask.run()V+42 j java.util.concurrent.ThreadPoolExecutor.runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V+95 j java.util.concurrent.ThreadPoolExecutor$Worker.run()V+5 j java.lang.Thread.run()V+11 v ~StubRoutines::call_stub
It seems that the native array clone method can not cope with an uninitialized array.
So far I could not reproduce this error on my intel i5 workstation.
© 2020 vmlens Legal Notice Privacy Policy