Search code examples
javakotlinhashcodeprimitive

System.identityHashCode() behavior on primitives


In jvm imgui, I was using

System.identityHashCode(i++)

where

var i = 0

to generate for each frame always a constant id for a given object (and so being
able to track it)

However, one user case just showed me that this is valid only for values in [0, 125]

Trying to debug and find the bug, I ended testing this short snippet of code:

    var i = 0
    val A = Array(256, { System.identityHashCode(i++) })
    i = 0
    val B = Array(256, { System.identityHashCode(i++) })
    repeat(256) {
        if (A[it] != B[it])
            println("[$it] different, A ${A[it]}, B ${B[it]}")
    }

also with:

  • bytes (fully working, A == B for all the 256 values)
  • shorts (not working from 128)
  • ints (not working from 128)
  • longs (not working from 128)
  • floats (not working at all)
  • doubles (not working at all)

Why is that?

And am I safe assuming this behavior will be the coherent also on other platforms?


Solution

  • However, one user case just showed me that this is valid only for values in [0, 125]

    System.identityHashCode(Object) takes an Object not a primitive which means that your i++ is auto-boxed to be an Integer (or Long or ...). The only time that the objects will have the same identity hashcode will most likely (but not always) be when they are the same object. As it happens, the JVM caches a small number of Integers for optimization purposes meaning that the identity hashcodes of the 0 to 127 values are be the same.

    When autoboxing happens the compiler generates a call to Integer.valueOf(int). If we look at the code for `valueOf(...) we see:

    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
    

    So there is a low and high cache range from which you will get a cached constant object that are generated when the JVM starts up. There are JVM arguments that affect the size of the number caches. So 127 must be within the cache range for your JVM while 128 and above are not.

    // both of the 100 values gets auto-boxed to the same Integer object
    System.identityHashCode(100) == System.identityHashCode(100)
    

    This is basically equivalent to comparing the hashcodes of the same object:

    Integer i = new Integer(100);
    System.identityHashCode(i) == System.identityHashCode(i)
    

    Once you are past the initial cached set of Integer values and larger ints are specified, new Integer objects will be created when they are auto-boxed and so the identity hashcodes are (most likely) no longer equal.

    // by default, each of these 1000000 values gets auto-boxed to a different object
    System.identityHashCode(1000000) != System.identityHashCode(1000000)
    

    This is basically equivalent to instantiating 2 different integers and expecting their hashcodes to be the same:

    Integer i1 = new Integer(1000000);
    Integer i2 = new Integer(1000000);
    System.identityHashCode(i1) != System.identityHashCode(i2)
    

    Note that System.identityHashCode(...) does not return unique values so it is possible (albeit unlikely) that they will generate the same value if when looking at different objects.