Reputation: 133
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.
Upvotes: 4
Views: 284
Reputation: 25855
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:
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.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.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:
x == -1
are optimized away as impossible.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. :)
Upvotes: 3