Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

java.lang.reflect.TypeVariable getBounds Is Not Thread Safe

DZone's Guide to

java.lang.reflect.TypeVariable getBounds Is Not Thread Safe

Calling java.lang.reflect.TypeVariable getBounds from multiple threads can lead to a race condition and could crash you JVM. See how to reproduce it.

· Java Zone ·
Free Resource

How do you break a Monolith into Microservices at Scale? This ebook shows strategies and techniques for building scalable and resilient microservices.

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<Y extends GenericInterface<Y>> {

}


If you call testGetBounds from multiple threads, calling getBounds leads to a race condition.

The Race Condition

Since the array of TypeVariables returned by getTypeParameters is cached in the volatile field genericInfo, each thread works on the same TypeVariable instance. And the class TypeVariableImpl implementing the TypeVariable interface modifies the not volatile field bounds without synchronization:

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.

Reproducing the Error

To reproduce the error, I used jcstress, an open JDK code tool:

"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.

Conclusion

java.lang.reflect.TypeVariable getBounds is not thread safe. Calling it from multiple threads might lead, depending on the java platform you are using, to strange errors.

I found this race condition with VMLens. VMLens traces Java applications and finds race conditions and deadlocks in the traced applications.

How do you break a Monolith into Microservices at Scale? This ebook shows strategies and techniques for building scalable and resilient microservices.

Topics:
java ,race conditions ,thread safe ,java performance

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}