Original: Little sister taste (wechat public ID: XJjdog), welcome to share, reprint, please keep the source.

Number crunching is the foundation of a language. If even 1+1 becomes untrustworthy, the entire program becomes untrustworthy.

Consider the following code:

Integer a = 1;
System.out.println(a);
Integer b = 2;
System.out.println( a.intValue() == b.intValue() );
System.out.println(a.equals(b));
Copy the code

The result of execution is:

-996
true
true
Copy the code

At this point, you still dare to write code?

Why is that?

Quite simply, we use reflection to change something.

The following code, which changes the execution logic of some basic operations, certainly falls into the category of burying holes. But let’s look at its behavior first.

public class StaticBlock {
    static {
        try{ Class<? > cls = Integer.class.getDeclaredClasses()[0];
            Field f = cls.getDeclaredField("cache");
            f.setAccessible(true);
            Integer[] cache = ((Integer[]) f.get(cls));
            for (int i = 0; i < cache.length; i++) {
                cache[i] = -996; }}catch (Exception e) {
            e.printStackTrace();
            //silence}}}Copy the code

The program uses reflection to modify the contents of the cache variable in Integer so that the number in the cache variable is a fixed value. We use -996 here, which means there’s never a 996.

As long as you manage to trigger this code, Java’s Integer wrapper class, it’s useless.

We can do that, but the key is cache variables.

Digital cache

There are eight basic types in Java, and due to the object-oriented nature of Java, they also have eight wrapper types, such as int and Integer. The value of the wrapper type can be null, and in many cases, they can be assigned to each other.

Considering this little piece of code, it’s been boxed and unboxed a lot.

public Integer cal(a) {
 Integer a = 1000;
 int b = a * 10;
 return b;
}
Copy the code

Let’s look at it at the bytecode level.

public java.lang.Integer read(a); descriptor: ()Ljava/lang/Integer; flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: sipush 1000 3: invokestatic#2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         6: astore_1
         7: aload_1
         8: invokevirtual #3 // Method java/lang/Integer.intValue:()I
        11: bipush        10
        13: imul
        14: istore_2
        15: iload_2
        16: invokestatic  #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        19: areturn
Copy the code

It can be seen that such a simple operation even involves valueOf, intValue and other methods for many times, indicating that the efficiency of its calculation process is less efficient than simple number operation.

The valueOf method, which wraps a normal number into an Integer, we trace to its method.

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
Copy the code

To improve the efficiency of the conversion, the Integer actually caches the mapping between I and Integer! So that the next time you use it, you can directly position it. The cache variable, that’s where this intermediate information is stored. If we change it by reflection, the Integer will have abnormal behavior!

More and more

IntegerCache, which caches Integer objects between low and high, can be modified by -xx :AutoBoxCacheMax.

String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
Copy the code

Interestingly, Long also has such a Cache, but its upper and lower limits are fixed, just like Byte and Short.

static final Long cache[] = new Long[-(-128) + 127 + 1]; SCopy the code

Double and Float are miserable, you can only just new one, you can’t do this kind of caching.

Overall, Integer is special. The output of the following code is uncertain even if we don’t do the reflection magic.

Integer n1 = 123;
Integer n2 = 123;
Integer n3 = 128;
Integer n4 = 128;

System.out.println(n1 == n2);
System.out.println(n3 == n4);
Copy the code

That’s because, normally, it would print true,false; When we increase the upper limit with AutoBoxCacheMax, it prints true,true. Comparison between indeed as expected object, or to use the equals to relatively by spectrum.

End

Looking at this chest-size pit, I can’t tell you how much I feel. The code as a whole should be easy to spot with a normal review, but there’s always a chance.

If this piece of code gets put online, even if it’s accidentally sent to the warehouse by some cute student practicing, the consequences can be devastating. The purpose of this code is pretty straightforward, but if we make the cache array modification logic a little bit more complicated, it will only trigger the modification of the value of a single variable under certain conditions, it will be fatal.

After all, even sonar can not scan out, and the JDK such private variables, there are a lot of waiting for us to explore!

Author introduction: Little sister taste (XJjdog), a programmer is not allowed to detour the public number. Focus on infrastructure and Linux. Ten years of architecture, 10 billion traffic per day, and you discuss the high concurrency world, give you a different taste. My personal wechat xJJdog0, welcome to add friends, further communication.