I just made this simple "program":
public static void main(String[] args) {
int i = 1;
int k = 0;
while (true) {
if(++i==0) System.out.println("loop: " + ++k);
}
}
Upon running this program, I immediately get the output:
(...)
loop: 881452
loop: 881453
loop: 881454
loop: 881455
loop: 881456
loop: 881457
loop: 881458
(...)
as if i
would be always 0.
And in fact, when I debug in Eclipse, upon suspending the program, i
would be always zero. When stepping through the loop, i
would increment, but upon resuming and suspending the debugger, i
is 0 again.
When I change i
to long, upon running the program I need to wait quite a while before seeing the first loop: 1
. In the debugger, upon pausing the program, i
does increment: it's not 0, so it works as it should.
What's the problem with ++i
as an int?
Simply put: You can't, because Integer is immutable and you only get the object address by value, so swapping the whole object is not possible because after the method finished, the old object gets reassigned.
If a method does not return a value, it must be declared to return void . However, the pop() method in the Stack class returns a reference data type: an object. Methods use the return operator to return a value. Any method that is not declared void must contain a return statement.
Integer objects are immutable, so you cannot modify the value once they have been created. You will need to create a new Integer and replace the existing one.
The java.lang.Integer.intValue () is an inbuilt method in java that returns the value of this integer as an int. Parameters: The method does not accept any parameters. Return Value : The method returns the numeric value which is represented by the object after conversion to the integer type. Program 1: For a positive integer.
To convert integer object to int primitive type in java we can use intValue () method of Integer Class . This method does not take any argument but Returns the value of this Integer as an int. There we will convert Integer to int using intValue () method as below example .
In fact, you're not changing the value of arr, either - it's a reference to the same array as before, but the value in the array has been changed. And that's what you can't do with Integer, because Integer is an immutable type. If you want a mutable class like Integer, you could use AtomicInteger instead:
It is a type of String which will be parsed into an integer object. This is of integer type and used in converting the string object. Returns an Integer instance holding the value of the specified parameter int i. Returns an Integer instance holding the value represented by the string argument.
If you keep incrementing an integer type, it will eventually overflow, becoming a large negative value. If you keep going, it will eventually become 0 again, and the cycle will repeat.
There are convenience methods to help avoid inadvertent overflows, like Math.addExact()
, but these wouldn't normally be used in a loop.
I know that it is overflowing. I'm just puzzled that it's overflowing THAT fast. And I find it strange that each time I suspend the debugger, i is 0.
When you suspend a running thread, consider the chance of the thread being in a slow call to println()
that traversing a huge stack of Java and native OS code, versus the probability of landing in the test of your while loop, which is just incrementing a local variable. You'd have to have a pretty quick trigger finger to see anything other than the print statement. Try stepping through instead.
When something happens 4 billion times in a row, it's a pretty good guess it will happen next time. Branch prediction will help here in any case, and it's possible that your optimizing runtime removes the increment operation and test entirely, since the intervening values of i
are never read.
As JohannesD suggested in a comment, it's hardly possible to count from 0 to Integer.MAX_VALUE
(and, after the overflow, from -Integer.MAX_VALUE
to 0 again) so quickly.
In order to verify the assumption that the JIT does some magic optimization here, I created a slightly modified program, introducing some methods make it easier to identify parts of the code:
class IntOverflowTest
{
public static void main(String[] args) {
runLoop();
}
public static void runLoop()
{
int i = 1;
int k = 0;
while (true) {
if(++i==0) doPrint(++k);
}
}
public static void doPrint(int k)
{
System.out.println("loop: " + k);
}
}
The bytecode emitted and shown with javap -c IntOverflowTest
brings no surprises:
class IntOverflowTest {
IntOverflowTest();
Code:
0: aload_0
1: invokespecial #1
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #2
3: return
public static void runLoop();
Code:
0: iconst_1
1: istore_0
2: iconst_0
3: istore_1
4: iinc 0, 1
7: iload_0
8: ifne 4
11: iinc 1, 1
14: iload_1
15: invokestatic #3
18: goto 4
public static void doPrint(int);
Code:
0: getstatic #4
3: new #5
6: dup
7: invokespecial #6
10: ldc #7
12: invokevirtual #8
15: iload_0
16: invokevirtual #9
19: invokevirtual #10
22: invokevirtual #11
25: return
}
It clearly does increment both local variables (runLoop
, offsets 4 and 11).
However, when running the code with -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -XX:+PrintAssembly
in a Hotspot Disassembler, the machine code eventually ends up to be the following:
Decoding compiled method 0x00000000025c2c50:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} {0x000000001bb40408} 'runLoop' '()V' in 'IntOverflowTest'
# [sp+0x20] (sp of caller)
0x00000000025c2da0: mov %eax,-0x6000(%rsp)
0x00000000025c2da7: push %rbp
0x00000000025c2da8: sub $0x10,%rsp ;*synchronization entry
; - IntOverflowTest::runLoop@-1 (line 10)
0x00000000025c2dac: mov $0x1,%ebp ;*iinc
; - IntOverflowTest::runLoop@11 (line 13)
0x00000000025c2db1: mov %ebp,%edx
0x00000000025c2db3: callq 0x00000000024f6360 ; OopMap{off=24}
;*invokestatic doPrint
; - IntOverflowTest::runLoop@15 (line 13)
; {static_call}
0x00000000025c2db8: inc %ebp ;*iinc
; - IntOverflowTest::runLoop@11 (line 13)
0x00000000025c2dba: jmp 0x00000000025c2db1 ;*invokestatic doPrint
; - IntOverflowTest::runLoop@15 (line 13)
0x00000000025c2dbc: mov %rax,%rdx
0x00000000025c2dbf: add $0x10,%rsp
0x00000000025c2dc3: pop %rbp
0x00000000025c2dc4: jmpq 0x00000000025b0d20 ; {runtime_call}
0x00000000025c2dc9: hlt
One can clearly see that it does not increment the outer variable i
any more. It only calls the doPrint
method, increments a single variable (k
in the code), and then and immediately jumps back to the point before the doPrint
call.
So the JIT indeed seems to detect that there is no real "condition" involved for printing the output, and that the code is equivalent to an infinite loop that only prints and increments a single variable.
This seems like a quite sophisticated optimization for me. I would expect that it's far from trivial to detect a case like this. But obviously, they managed to do so...
Your loop is overflowing i
. You have no break
, so after a period of time, i
wraps back to 0, and this prints the statement and increments k
. This also explains why changing the int
to a long
causes the printing to slow down: it takes much longer for a long
value to overflow.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With