JDK-8297186 : G1 triggers unnecessary full GCs when heap utilization is low
  • Type: Bug
  • Component: hotspot
  • Sub-Component: gc
  • Affected Version: 20
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2022-11-17
  • Updated: 2022-12-18
  • Resolved: 2022-12-06
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 20
20 b27Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Description
After JDK-8253413, GC could unnecessarily trigger full GCs even when heap utilization is low. I discovered this case with the test hotspot/jtreg/gc/class_unloading/TestG1ClassUnloadingHWM.java.

To reproduce, add "-XX:G1HeapRegionSize=32m" to the run() function of TestG1ClassUnloadingHWM.java for launching TestG1ClassUnloadingHWM.AllocateBeyondMetaspaceSize. Optionally, also set a fixed heap size such as -Xms512m -Xmx512m to make the problem more obvious. Then the testWithG1ClassUnloading() case would fail, with GC log looks like:

[0.006s][info][gc] Using G1
[0.065s][info][gc] GC(0) Pause Young (Concurrent Start) (Metadata GC Threshold) 34M->33M(512M) 4.710ms
[0.065s][info][gc] GC(1) Concurrent Mark Cycle
[0.069s][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 33M->32M(512M) 1.386ms
[0.079s][info][gc] GC(3) Pause Full (G1 Compaction Pause) 32M->32M(512M) 9.201ms
[0.079s][info][gc] GC(1) Concurrent Mark Cycle 13.463ms
[0.088s][info][gc] GC(4) Pause Young (Concurrent Start) (Metadata GC Threshold) 32M->32M(512M) 0.891ms
[0.088s][info][gc] GC(5) Concurrent Mark Cycle
[0.091s][info][gc] GC(6) Pause Young (Normal) (G1 Evacuation Pause) 32M->32M(512M) 0.621ms
[0.097s][info][gc] GC(7) Pause Full (G1 Compaction Pause) 32M->32M(512M) 5.227ms
[0.097s][info][gc] GC(5) Concurrent Mark Cycle 8.723ms
[0.108s][info][gc] GC(8) Pause Young (Normal) (G1 Evacuation Pause) 64M->32M(512M) 0.668ms
[0.113s][info][gc] GC(9) Pause Full (G1 Compaction Pause) 32M->32M(512M) 5.266ms
[0.120s][info][gc] GC(10) Pause Young (Normal) (G1 Evacuation Pause) 64M->32M(512M) 0.665ms
[0.124s][info][gc] GC(11) Pause Full (G1 Compaction Pause) 32M->32M(512M) 4.376ms
Comments
Changeset: a9e6c62b Author: Thomas Schatzl <tschatzl@openjdk.org> Date: 2022-12-06 10:09:59 +0000 URL: https://git.openjdk.org/jdk/commit/a9e6c62ba7df8d28cef9579c57a0386736bd9dbf
06-12-2022

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/11209 Date: 2022-11-17 13:14:12 +0000
17-11-2022

Draft PR: https://github.com/openjdk/jdk/pull/11209 The problem has been known and explicitly attempted to be worked around, but it did not work for *that* small heap with huge region sizes. With this change G1 tries to ensure that after GC we are going to use at least one eden region in a better way (I believe). The change is missing a test still though, but it passes the given example.
17-11-2022

Context: our internal JDK uses a larger default G1HeapRegionSize, which is 32M when Xmx is ~32G. This issue seems a corner case when there's only 1-2 live heap regions, so it is probably not critical for real-world workload. But it is still worth fixing. I have narrowed down the cause to JDK-8253413, but haven't looked into exactly why yet. [~tschatzl] if you have any suggestion or idea on how to fix this, feel free to chime in or take over this RFE.
17-11-2022