4
public static void main(String[] args) throws Exception {
    for (int i = 0, k = 20; i < Integer.MAX_VALUE; i++) {
        final Byte b = (byte) -1; // new Byte((byte) -1) works fine
        final int x = b.byteValue() & 0xff;
        if (x == -1) System.out.println(x + " is -1 (" + i + ")");
        if (x != 255) {
            System.out.println(x + " is not 255 (" + i + ")");
            if (x == -1) System.out.println(x + " is not 255 but -1 (" + i + ")");
            if (--k == 0) break;
        }
        if (i % 100 == 0) Thread.sleep(1); // or other operations to make this code run slower
    }
}

Output of a run:

-1 is not 255 (110675)
-1 is not 255 but -1 (110675)
/ is not 255 (168018)
/ is not 255 (168019)
/ is not 255 (168020)
/ is not 255 (168021)
/ is not 255 (168022)
/ is not 255 (168023)
/ is not 255 (168024)
/ is not 255 (168025)
/ is not 255 (168026)
/ is not 255 (168027)
/ is not 255 (168028)
/ is not 255 (168029)
/ is not 255 (168030)
/ is not 255 (168031)
/ is not 255 (168032)
/ is not 255 (168033)
/ is not 255 (168034)
/ is not 255 (168035)
/ is not 255 (168036)

who can explain the output result. the test must be running at jdk1.8.0_20 or jdk1.8.0_25 or jdk1.8.0_31 and with "-server" option. I think this is a bug, and I have submitted a bug report to oracle, but haven't received a reply yet.

fei
  • 133
  • 4
  • 2
    What is the output? You haven't described it. – ryanyuyu Jan 28 '15 at 14:00
  • 2
    What is your expectation, and what is the output? Seeing that code, I would expect, that nothing should be printed. – Seelenvirtuose Jan 28 '15 at 14:02
  • If even Oracle cannot explain what's going on, how can you expect the community at SO to respond to your bug report? – Sergey Kalinichenko Jan 28 '15 at 14:02
  • I vaguely remember a bug related to `byte` boxing/unboxing. This is probably it. – Marko Topolnik Jan 28 '15 at 14:06
  • 1
    That should not print anything and run more than 200 days. If it prints something, breaks on the k == 0 or so, there is a bug. Filing it with Oracle is the right way to go, but don't expect instant reactions. – Mnementh Jan 28 '15 at 14:08
  • 1
    The output is utterly crazy: the same check `x == -1` first fails, then passes, and `/` is printed as an integer value. Not to mention the obvious failure. This can only be the deeds of the JIT compiler. – Marko Topolnik Jan 28 '15 at 14:18
  • @MarkoTopolnik Your hypothesis about boxing/unboxing is supported by the fact that changing the code to use a primitive byte makes the problem go away. – VGR Jan 28 '15 at 19:42
  • 1
    Also using the String constructor makes the problem go away - e.g. final Byte b = new Byte("-1"); – spudone Jan 29 '15 at 01:21
  • ***Why?*** Why not just use the value itself? Cast to a `byte`? And where is the alleged `Byte.valueOf()` call anyway? – user207421 Jan 29 '15 at 06:06
  • 1
    This sort of result might be achievable with some malicious use of reflection. Did anyone recently get fired? – user253751 Jan 29 '15 at 06:29
  • 1
    FWIW, I'm running Oracle's JRE 1.8.0_40-ea, and it is indeed not printing anything, while 1.8.0-31 on another system reproduces the output in the question. – Dolda2000 Jan 29 '15 at 06:49
  • @EJP because sometimes you must using Object as parameter to call a method for example java.util.Map.get(Object key), this is just demo code I hope you can understand that. – fei Jan 29 '15 at 06:56
  • @Dolda2000 that means it will be fixed in next release right? – fei Jan 29 '15 at 06:58
  • As an observation, a difference between using auto-boxing and `new Byte()` (which indeed "fixes" the problem), auto-boxing uses `Byte.valueOf` instead. Using `Byte.valueOf` manually also reproduces the problem, so that seems to be the core of the issue, at least. – Dolda2000 Jan 29 '15 at 07:00

1 Answers1

3

I think I found the issue that causes this behavior; it was filed with OpenJDK as bug 8042786 and is, indeed, related to autoboxing. Running Java with -XX:-EliminateAutoBox seems to be a valid workaround and makes this problem disappear.

It is marked as resolved. The linked bug reports are an interesting read, and the bug was apparently originally found in a SO question.

EDIT:

Judging from the bug reports and a compilation log from a VM with this problem, this is my analysis of what, more precisely, happened:

For the first 110674 iterations, the code runs in the interpreter and as compiled by the C1 compiler that doesn't have this bug, but after that, the C2 compiler kicks in to replace main with an even more optimized version. In this first round of compilation, C2 does the following:

  • From the expression b.byteValue() & 0xff, it draws the conclusion that x is between 0 and 255. Therefore, the tests for x == -1 cannot be true, and are optimized away.
  • At this point, the class java.lang.System still hasn't been loaded, so all the calls to System.out.println are replaced with traps that deoptimize the method and revert back into the interpreter awaiting class loading and recompilation.
  • To evaluate the b.byteValue() & 0xff expression, the compiler is smart enough to inline an unsigned memory byte load, to bring the value in from memory without having to do a following and operation on it, so that single instruction alone is supposed to fill the value of x, but this is where the compiler bug comes in and mistakenly replaces this with a signed load (a movsbl instruction). So x is actually loaded with complete sign extension to 32 bits, but the compiler has already assumed that the value of x be in the range 0-255.

What then happens is that the x == -1 test does not trigger since C2 has optimized it away, but the x != 255 test does trigger, because the compiler hasn't been smart enough to see that x should always be 255 (I do find this slightly strange since both Byte.valueOf() and b.byteValue() have been inlined, but apparently it is what it is). When it enters the following clause, the code attempts to load System.out, which promptly deoptimizes the code, loads the class, and finishes the iteration in the interpreter. This is why x is "properly" treated as -1 for the rest of this iteration.

Then the loop continues for another couple of thousand iterations in the interpreter and C1-compiled code until C2 kicks in again to reoptimize the code now with java.lang.System loaded. What then happens is the following:

  • Again, the tests for x == -1 are optimized away as impossible.
  • However, this time java.lang.System is loaded, and the calls to format x + " is not 255 (" + i + ")" are inlined. Looking at the source of Integer.toString(), this means that the test for output of negative integers is eliminated (again, as x is determined to be positive), and thus x is formatted as if it were a positive number of 1 digit (since it is less than 10). Therefore, this one digit is output as '0' + -1; that is, as /.

This might have been trivia at best, but it caught my mind. :)

Community
  • 1
  • 1
Dolda2000
  • 25,216
  • 4
  • 51
  • 92
  • Nice workaround, but we decided to rollback to jdk1.8.0_11 instead and waiting for jdk1.8.0_40, thank you anyway. – fei Jan 29 '15 at 14:19
  • @fei: If you want your actual question, *"who can explain the output result"*, answered, see my edit. ;) – Dolda2000 Jan 29 '15 at 17:51