0

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?

elect
  • 6,765
  • 10
  • 53
  • 119
  • 3
    This is because the primtive is autoboxed to `Integer`, and then this takes over: https://stackoverflow.com/questions/1514910/how-to-properly-compare-two-integers-in-java :) – Oliver Charlesworth Dec 06 '17 at 15:02
  • Related: https://stackoverflow.com/questions/15052216/how-large-is-the-integer-cache and https://stackoverflow.com/questions/3131136/integers-caching-in-java – Mark Rotteveel Dec 06 '17 at 16:03

1 Answers1

5

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.

Gray
  • 115,027
  • 24
  • 293
  • 354
  • Thanks, clear. So I suppose regarding bytes, they all get cached.. Anyway, I do need to compare references, that's correct – elect Dec 06 '17 at 15:16
  • 1
    Isn't he creating an array with the values of the function `System.identityHashCode` and the problem is rather that for the non cached values that function just returns non deterministic values? `Integer.valueOf(System.identityHashCode(255)).equals(Integer.valueOf(System.identityHashCode(255)))` will almost allways return false, regardless of the use of equals. – OH GOD SPIDERS Dec 06 '17 at 15:18
  • Duh. Right @OHGODSPIDERS. Fixed. – Gray Dec 06 '17 at 15:53
  • Your `new Integer`s should be replaced by `Integer.valueOf`: `new Integer(100)` is not and can't be cached! – Alexey Romanov Dec 07 '17 at 07:16
  • Ah, sorry, I misread your second code block. It's still not equivalent to the first one, but not the error I thought at the first glance. It should be `Integer i1 = Integer.valueOf(100); Integer i2 = Integer.valueOf(100); System.identityHashCode(i1) == System.identityHashCode(i2)`. – Alexey Romanov Dec 07 '17 at 14:01
  • Sigh. You aren't getting it @AlexeyRomanov. I'm trying to show that for low numbers, the objects are the same if you autobox from them. However for large numbers they aren't so they are separate objects. Have you looked at `Integer.valueOf()` because it does a `new Integer(int)` unless the number is within the cache range. That's the entire point of the sample code. – Gray Dec 07 '17 at 14:42
  • Also @AlexeyRomanov, then a number is autoboxed, the compiler generates an `Integer.valueOf(int)` call anyway. – Gray Dec 07 '17 at 14:47
  • I understand what it does quite well; I was saying that your answer didn't make it clear. Now it's much better. – Alexey Romanov Dec 08 '17 at 06:44