-
Bug
-
Resolution: Duplicate
-
P4
-
17.0.15
-
generic
-
generic
ADDITIONAL SYSTEM INFORMATION :
Reproduced on Linux Ubuntu 24.04
Both with JDK17 and JDK21 (including JDK17 nightly build 2025-05-27).
OpenJDK Runtime Environment (build 17.0.15+6-Ubuntu-0ubuntu124.04)
OpenJDK Runtime Environment (build 21.0.7+6-Ubuntu-0ubuntu124.04)
OpenJDK Runtime Environment (build 17.0.16-testing+0-builds.shipilev.net-openjdk-jdk17-dev-b818-20250527-1808)
A DESCRIPTION OF THE PROBLEM :
The C2 Jit compiler sometimes may reorder the Unsafe.getLong and Unsafe.putLong operations so that logic is broken. Problem is reproduced only with the ShenandoahGC and if method is C2 compiled.
Other options work fine:
1) jdk11 + any GC - OK
2) jdk17/jdk21 + G1GC or ZGC - OK
3) jdk17/jdk21 + ShenandoahGC + -XX:TieredStopAtLevel=3 - OK
***
For example such Java method would return 0L even if there is an non-zero long value stored in the buffer before call:
public long run(long addr) {
long tmp = UNSAFE.getLong(addr);
UNSAFE.putLong(addr, 0L);
if (dummy != null)
System.err.println("never happen");
return tmp;
}
Tests show that it is important that run() is method of the inner class (non-static). And dummy is field of the outer class.
***
Reordering is obvious in the generated assembler code. Below are fragments obtained running the testcase attached.
SenandoahGC. WRONG: write is reordered to be before the read:
# {method} {0x00007f4738401510} 'run' '(J)J' in 'Test$TestRunner'
# this: rsi:rsi = 'Test$TestRunner'
# parm0: rdx:rdx = long
.... skipped
0x00007f47e0fb0b67: mov rbx, rdx
0x00007f47e0fb0b6a: mov qword ptr [rbx], r12 <------------ write 0L to buffer
0x00007f47e0fb0b6d: mov r8, r11
0x00007f47e0fb0b70: shl r8, 3
0x00007f47e0fb0b74: test byte ptr [r15 + 0x20], 1
0x00007f47e0fb0b79: jne L0002
L0001: mov r11d, dword ptr [r8 + 0xc]
0x00007f47e0fb0b7f: mov rbx, qword ptr [rbx] <------------ read form the buffer
G1GC. CORRECT: read goes first before the write:
# {method} {0x000072317b401510} 'run' '(J)J' in 'Test$TestRunner'
# this: rsi:rsi = 'Test$TestRunner'
# parm0: rdx:rdx = long
.... skipped
0x0000723214faa950: mov r11, rdx
0x0000723214faa953: mov rax, qword ptr [r11] <------------ read from the buffer
0x0000723214faa956: mov qword ptr [r11], r12 <------------ write 0L to buffer
***
Something more or less similar was fixed in the https://bugs.openjdk.org/browse/JDK-8220714
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
javac Test.java
java -XX:+UseShenandoahGC Test
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
All iterations are OK in the test:
Test start
0 iter passed
1000000 iter passed
2000000 iter passed
3000000 iter passed
4000000 iter passed
5000000 iter passed
6000000 iter passed
7000000 iter passed
8000000 iter passed
9000000 iter passed
ACTUAL -
"RuntimeException: unexpected 0L in buffer" in some iteration (once the run() is C2 compiled)
Test start
0 iter passed
1000000 iter passed
2000000 iter passed
3000000 iter passed
4000000 iter passed
5000000 iter passed
Exception in thread "main" java.lang.RuntimeException: unexpected 0L in buffer
at Test.test(Test.java:24)
at Test.main(Test.java:16)
---------- BEGIN SOURCE ----------
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class Test {
private static final Unsafe UNSAFE = getUnsafe();
private static final int BUFFERS_COUNT = 10_000_000;
private static final long[] buffers = allocateBuffers(BUFFERS_COUNT);
// Looks like everything would work OK if make this field static.
private final Object dummy = null;
private final TestRunner testRunner = new TestRunner();
public static void main(String[] args) {
new Test().test();
}
private void test() {
System.err.println("Test start");
for (int i = 0; i < BUFFERS_COUNT; i++) {
if (testRunner.run(buffers[i]) == 0)
throw new RuntimeException("unexpected 0L in buffer");
if (i % (BUFFERS_COUNT / 10) == 0)
System.err.println(i + " iter passed");
}
}
// Everything would work OK if make TestRunner class static or
// get rid of it and move run() method directly to Test class.
public final class TestRunner {
public long run(long addr) {
// Bug is here.
//
// getLong and putLong are reordered and tmp becomes 0L.
// But it can not be so since all buffers were filled with non-zero value after allocation.
long tmp = UNSAFE.getLong(addr);
// Everything would work OK if uncomment this check.
// if (tmp == 0)
// throw new RuntimeException("unexpected zero in buffer");
UNSAFE.putLong(addr, 0L);
// Everything would work OK if remove access and check for this dummy field.
if (dummy != null)
System.err.println("never happen");
return tmp;
}
}
private static Unsafe getUnsafe() {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
return (Unsafe) theUnsafe.get(null);
}
catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private static long[] allocateBuffers(int count) {
long[] buffers = new long[count];
for (int i = 0; i < count; i++) {
buffers[i] = UNSAFE.allocateMemory(Long.BYTES);
// Put non-zero value to buffer.
UNSAFE.putLong(buffers[i], 333555777L);
}
return buffers;
}
}
---------- END SOURCE ----------
Reproduced on Linux Ubuntu 24.04
Both with JDK17 and JDK21 (including JDK17 nightly build 2025-05-27).
OpenJDK Runtime Environment (build 17.0.15+6-Ubuntu-0ubuntu124.04)
OpenJDK Runtime Environment (build 21.0.7+6-Ubuntu-0ubuntu124.04)
OpenJDK Runtime Environment (build 17.0.16-testing+0-builds.shipilev.net-openjdk-jdk17-dev-b818-20250527-1808)
A DESCRIPTION OF THE PROBLEM :
The C2 Jit compiler sometimes may reorder the Unsafe.getLong and Unsafe.putLong operations so that logic is broken. Problem is reproduced only with the ShenandoahGC and if method is C2 compiled.
Other options work fine:
1) jdk11 + any GC - OK
2) jdk17/jdk21 + G1GC or ZGC - OK
3) jdk17/jdk21 + ShenandoahGC + -XX:TieredStopAtLevel=3 - OK
***
For example such Java method would return 0L even if there is an non-zero long value stored in the buffer before call:
public long run(long addr) {
long tmp = UNSAFE.getLong(addr);
UNSAFE.putLong(addr, 0L);
if (dummy != null)
System.err.println("never happen");
return tmp;
}
Tests show that it is important that run() is method of the inner class (non-static). And dummy is field of the outer class.
***
Reordering is obvious in the generated assembler code. Below are fragments obtained running the testcase attached.
SenandoahGC. WRONG: write is reordered to be before the read:
# {method} {0x00007f4738401510} 'run' '(J)J' in 'Test$TestRunner'
# this: rsi:rsi = 'Test$TestRunner'
# parm0: rdx:rdx = long
.... skipped
0x00007f47e0fb0b67: mov rbx, rdx
0x00007f47e0fb0b6a: mov qword ptr [rbx], r12 <------------ write 0L to buffer
0x00007f47e0fb0b6d: mov r8, r11
0x00007f47e0fb0b70: shl r8, 3
0x00007f47e0fb0b74: test byte ptr [r15 + 0x20], 1
0x00007f47e0fb0b79: jne L0002
L0001: mov r11d, dword ptr [r8 + 0xc]
0x00007f47e0fb0b7f: mov rbx, qword ptr [rbx] <------------ read form the buffer
G1GC. CORRECT: read goes first before the write:
# {method} {0x000072317b401510} 'run' '(J)J' in 'Test$TestRunner'
# this: rsi:rsi = 'Test$TestRunner'
# parm0: rdx:rdx = long
.... skipped
0x0000723214faa950: mov r11, rdx
0x0000723214faa953: mov rax, qword ptr [r11] <------------ read from the buffer
0x0000723214faa956: mov qword ptr [r11], r12 <------------ write 0L to buffer
***
Something more or less similar was fixed in the https://bugs.openjdk.org/browse/JDK-8220714
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
javac Test.java
java -XX:+UseShenandoahGC Test
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
All iterations are OK in the test:
Test start
0 iter passed
1000000 iter passed
2000000 iter passed
3000000 iter passed
4000000 iter passed
5000000 iter passed
6000000 iter passed
7000000 iter passed
8000000 iter passed
9000000 iter passed
ACTUAL -
"RuntimeException: unexpected 0L in buffer" in some iteration (once the run() is C2 compiled)
Test start
0 iter passed
1000000 iter passed
2000000 iter passed
3000000 iter passed
4000000 iter passed
5000000 iter passed
Exception in thread "main" java.lang.RuntimeException: unexpected 0L in buffer
at Test.test(Test.java:24)
at Test.main(Test.java:16)
---------- BEGIN SOURCE ----------
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class Test {
private static final Unsafe UNSAFE = getUnsafe();
private static final int BUFFERS_COUNT = 10_000_000;
private static final long[] buffers = allocateBuffers(BUFFERS_COUNT);
// Looks like everything would work OK if make this field static.
private final Object dummy = null;
private final TestRunner testRunner = new TestRunner();
public static void main(String[] args) {
new Test().test();
}
private void test() {
System.err.println("Test start");
for (int i = 0; i < BUFFERS_COUNT; i++) {
if (testRunner.run(buffers[i]) == 0)
throw new RuntimeException("unexpected 0L in buffer");
if (i % (BUFFERS_COUNT / 10) == 0)
System.err.println(i + " iter passed");
}
}
// Everything would work OK if make TestRunner class static or
// get rid of it and move run() method directly to Test class.
public final class TestRunner {
public long run(long addr) {
// Bug is here.
//
// getLong and putLong are reordered and tmp becomes 0L.
// But it can not be so since all buffers were filled with non-zero value after allocation.
long tmp = UNSAFE.getLong(addr);
// Everything would work OK if uncomment this check.
// if (tmp == 0)
// throw new RuntimeException("unexpected zero in buffer");
UNSAFE.putLong(addr, 0L);
// Everything would work OK if remove access and check for this dummy field.
if (dummy != null)
System.err.println("never happen");
return tmp;
}
}
private static Unsafe getUnsafe() {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
return (Unsafe) theUnsafe.get(null);
}
catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private static long[] allocateBuffers(int count) {
long[] buffers = new long[count];
for (int i = 0; i < count; i++) {
buffers[i] = UNSAFE.allocateMemory(Long.BYTES);
// Put non-zero value to buffer.
UNSAFE.putLong(buffers[i], 333555777L);
}
return buffers;
}
}
---------- END SOURCE ----------
- duplicates
-
JDK-8358334 C2/Shenandoah: incorrect execution with Unsafe
-
- Resolved
-