The simple way to see it is like this:
Step 1: Your int i = 0; line, which (of course) does this:
i = 0
Then we come to the i = i++; line, where things get interesting. The right-hand side of the = is evaluated, and then assigned to the left-hand side. So let's look at the right-hand side of that, i++, which has two parts:
Step 2:
temporary_holder_for_value = i
The value of i is read and stored away in a temporary location (one of the virtual machine registers, I expect). Then the second part of i++ is done:
Step 3:
i = i + 1
Now we're done with the right-hand side, and we assign the result to the left-hand side:
Step 4:
i = temporary_holder_for_value
The key is that last step. Basically, everything to the right of the = is done first, and the result of it is then assigned to the left. Because you used a post-increment (i++, not ++i), the result of the expression on the right takes i's value before the increment. And then the last thing is to assign that value to the left-hand side.