-
Type:
Bug
-
Resolution: Unresolved
-
Priority:
P3
-
None
-
Affects Version/s: 25.0.1
-
Component/s: hotspot
-
x86_64
-
os_x
ADDITIONAL SYSTEM INFORMATION :
Tested on Linux x86_64 and macOS (Apple Silicon).
The issue reproduces consistently across different operating systems and hardware platforms, suggesting that it is not OS-specific.
Observed with the following JVM configurations:
• JDK 21 + Shenandoah GC
• JDK 25 + Shenandoah GC
• JDK 25 + ZGC
For comparison:
• JDK 11 / JDK 17 with any GC (including Shenandoah, ZGC, G1, Parallel) do not show the issue.
• JDK 21 / JDK 25 with non-concurrent GCs (G1, Parallel) also do not show the issue.
A DESCRIPTION OF THE PROBLEM :
A significant and unexpected performance regression is observed in JDK 21 and later when running a simple allocation-heavy loop that creates multiple SoftReference, WeakReference, and PhantomReference objects, combined with allocations of mixed-size byte arrays (including ~2MB objects).
The issue manifests as a large increase in total execution time when using concurrent, moving garbage collectors (Shenandoah and ZGC), compared to:
• Earlier JDK versions (JDK 11, JDK 17)
• Other GC algorithms (G1, Parallel)
No exceptions, synchronization, I/O, or explicit garbage collection calls are involved. The slowdown appears to be purely related to allocation and GC behavior.
Notably:
• On JDK 21:
• Shenandoah GC shows a severe slowdown (approximately an order of magnitude slower).
• On JDK 25:
• Both Shenandoah and ZGC perform better than JDK 21 Shenandoah, but remain significantly slower than earlier JDKs and non-concurrent GCs.
This behavior suggests a regression or pathological performance case related to reference processing under concurrent GCs introduced after JDK 17.
This issue might be related to the interaction between reference processing and concurrent, moving garbage collectors.
Possible contributing factors include:
• Increased mutator-side overhead for discovering and tracking SoftReference, WeakReference, and PhantomReference objects under concurrent marking.
• Reduced amortization of reference processing costs when references are short-lived and frequently allocated.
• Additional overhead triggered by mixed-size allocations, particularly large (~2MB) objects, which may interact differently with region-based memory management in concurrent GCs.
These observations are speculative; further investigation would be required to determine the exact root cause.
REGRESSION : Last worked in version 17.0.17
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Compile the attached Test.java.
2. Run the program using different JDK versions and garbage collectors, for example:
• java -XX:+UseShenandoahGC Test
• java -XX:+UseZGC Test
3. Measure total execution time (e.g., using time or similar tools).
4. Compare results across JDK versions and GC configurations.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Execution time should be roughly comparable across JDK versions and GC implementations, as the program performs only simple object allocations and does not rely on GC-specific features.
In particular, performance should not regress significantly compared to JDK 17 for similar GC algorithms.
ACTUAL -
Observed execution times (approximate):
• JDK 21 + ZGC: ~2–3 seconds
• JDK 21 + Shenandoah GC: ~24–27 seconds
• JDK 25 + ZGC: ~7 seconds
• JDK 25 + Shenandoah GC: ~7 seconds
• JDK 21/25 + other GC: ~2–3 seconds
The slowdown is reproducible and consistent across runs.
---------- BEGIN SOURCE ----------
import java.lang.ref.*;
public class Test {
static final ReferenceQueue<Object> RQ = new ReferenceQueue<>();
public static void main(String[] args) {
for (int i = 0; i < 100000; i++) {
// Mixed-size allocations
byte[] b1 = new byte[1];
byte[] b2 = new byte[8192];
byte[] b3 = new byte[2097152]; // ~2MB
byte[] b4 = new byte[256];
Object obj = new Object();
// Multiple reference types
new SoftReference<>(obj);
new WeakReference<>(obj);
new PhantomReference<>(obj, RQ);
// Nested references inside an object
new Container(obj, obj, obj, obj);
}
}
static class Container {
Object f1;
SoftReference<Object> f2;
WeakReference<Object> f3;
PhantomReference<Object> f4;
Container(Object a, Object b, Object c, Object d) {
f1 = a;
f2 = new SoftReference<>(b);
f3 = new WeakReference<>(c);
f4 = new PhantomReference<>(d, RQ);
}
}
}
---------- END SOURCE ----------
Tested on Linux x86_64 and macOS (Apple Silicon).
The issue reproduces consistently across different operating systems and hardware platforms, suggesting that it is not OS-specific.
Observed with the following JVM configurations:
• JDK 21 + Shenandoah GC
• JDK 25 + Shenandoah GC
• JDK 25 + ZGC
For comparison:
• JDK 11 / JDK 17 with any GC (including Shenandoah, ZGC, G1, Parallel) do not show the issue.
• JDK 21 / JDK 25 with non-concurrent GCs (G1, Parallel) also do not show the issue.
A DESCRIPTION OF THE PROBLEM :
A significant and unexpected performance regression is observed in JDK 21 and later when running a simple allocation-heavy loop that creates multiple SoftReference, WeakReference, and PhantomReference objects, combined with allocations of mixed-size byte arrays (including ~2MB objects).
The issue manifests as a large increase in total execution time when using concurrent, moving garbage collectors (Shenandoah and ZGC), compared to:
• Earlier JDK versions (JDK 11, JDK 17)
• Other GC algorithms (G1, Parallel)
No exceptions, synchronization, I/O, or explicit garbage collection calls are involved. The slowdown appears to be purely related to allocation and GC behavior.
Notably:
• On JDK 21:
• Shenandoah GC shows a severe slowdown (approximately an order of magnitude slower).
• On JDK 25:
• Both Shenandoah and ZGC perform better than JDK 21 Shenandoah, but remain significantly slower than earlier JDKs and non-concurrent GCs.
This behavior suggests a regression or pathological performance case related to reference processing under concurrent GCs introduced after JDK 17.
This issue might be related to the interaction between reference processing and concurrent, moving garbage collectors.
Possible contributing factors include:
• Increased mutator-side overhead for discovering and tracking SoftReference, WeakReference, and PhantomReference objects under concurrent marking.
• Reduced amortization of reference processing costs when references are short-lived and frequently allocated.
• Additional overhead triggered by mixed-size allocations, particularly large (~2MB) objects, which may interact differently with region-based memory management in concurrent GCs.
These observations are speculative; further investigation would be required to determine the exact root cause.
REGRESSION : Last worked in version 17.0.17
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Compile the attached Test.java.
2. Run the program using different JDK versions and garbage collectors, for example:
• java -XX:+UseShenandoahGC Test
• java -XX:+UseZGC Test
3. Measure total execution time (e.g., using time or similar tools).
4. Compare results across JDK versions and GC configurations.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Execution time should be roughly comparable across JDK versions and GC implementations, as the program performs only simple object allocations and does not rely on GC-specific features.
In particular, performance should not regress significantly compared to JDK 17 for similar GC algorithms.
ACTUAL -
Observed execution times (approximate):
• JDK 21 + ZGC: ~2–3 seconds
• JDK 21 + Shenandoah GC: ~24–27 seconds
• JDK 25 + ZGC: ~7 seconds
• JDK 25 + Shenandoah GC: ~7 seconds
• JDK 21/25 + other GC: ~2–3 seconds
The slowdown is reproducible and consistent across runs.
---------- BEGIN SOURCE ----------
import java.lang.ref.*;
public class Test {
static final ReferenceQueue<Object> RQ = new ReferenceQueue<>();
public static void main(String[] args) {
for (int i = 0; i < 100000; i++) {
// Mixed-size allocations
byte[] b1 = new byte[1];
byte[] b2 = new byte[8192];
byte[] b3 = new byte[2097152]; // ~2MB
byte[] b4 = new byte[256];
Object obj = new Object();
// Multiple reference types
new SoftReference<>(obj);
new WeakReference<>(obj);
new PhantomReference<>(obj, RQ);
// Nested references inside an object
new Container(obj, obj, obj, obj);
}
}
static class Container {
Object f1;
SoftReference<Object> f2;
WeakReference<Object> f3;
PhantomReference<Object> f4;
Container(Object a, Object b, Object c, Object d) {
f1 = a;
f2 = new SoftReference<>(b);
f3 = new WeakReference<>(c);
f4 = new PhantomReference<>(d, RQ);
}
}
}
---------- END SOURCE ----------