Hero Image
- Mihai Surdeanu

Java - Something strange happens under the hood

Today, we will continue the series of articles about Java as programming language and we will try to present a case when strange things happens under the hood. As a result, we can end up with different production issues.

Let's suppose we have the following code in production:

package ro.mihaisurdeanu;

public class Main {

    public static void main(String[] args) {
        Integer a = 100;
        Integer b = 100;

        System.out.println(a == b);
    }

}

Can you guess the output printed? Correct answer: true.

For sure this is not the expected answer. Why? You probably remember the purpose of "==" operator. This operator is used to compare the equality between two object references. If the answer is true, for sure the reference is the same. In other words, a and b points to the same object in memory. Really nice!

To understand what happens under the hood, an idea will be to actually see the implementation for static method Integer.valueOf – one pattern for creating a new integer: (OpenJDK 11)

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

Behind the scene there is a nice caching system to avoid creating new instances and to optimize memory usage.

   /**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * jdk.internal.misc.VM class.
     */

    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer[] cache;
        static Integer[] archivedCache;

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            // Load IntegerCache.archivedCache from archive, if possible
            VM.initializeFromArchive(IntegerCache.class);
            int size = (high - low) + 1;

            // Use the archived cache if it exists and is large enough
            if (archivedCache == null || size > archivedCache.length) {
                Integer[] c = new Integer;
                int j = low;
                for(int k = 0; k < c.length; k++)
                    c[k] = new Integer(j++);
                archivedCache = c;
            }
            cache = archivedCache;
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

Please also note that Integer and Long are immutable. Everything between [-128, 127] is cached by default. As you can see, you can also play with some VM properties to extend or reduce the default range.

In fact, this an example of a flyweight pattern. ;)

Other Related Posts:

Microbenchmarking with Java

Microbenchmarking with Java

Hello guys,

Today, we are going to continue the series of articles about Java ecosystem. More specific, today, we are going to talk about JMH (Java Microbenchmark Harness).

2nd Apr 2022 - Mihai Surdeanu