3737 * treated as permanently low rather than guessed at.
3838 *
3939 * <p>Antagonists poll {@link #pace()} once per loop iteration — a volatile
40- * read plus, only while throttled, a short sleep.
40+ * read plus, only while throttled, a short sleep. Above a second, higher
41+ * critical watermark, {@code pace()} sleeps much longer and the sampler
42+ * fires a one-off {@link System#gc()} to force reclaim before OOME hits —
43+ * plain throttling wasn't always enough to stop a heap OOME once several
44+ * antagonists kept allocating past the regular watermark (job 1861234807).
4145 */
4246final class MemoryGovernor {
4347
@@ -47,8 +51,13 @@ final class MemoryGovernor {
4751 // shed off-heap first), so back off earlier and require more slack.
4852 private static final double HEAP_HIGH_WATERMARK = 0.80 ;
4953 private static final double HEAP_LOW_WATERMARK = 0.65 ;
54+ // Critical tier: same signals, higher bar. Crossing this calls for a
55+ // harder brake than pace()'s regular 5ms sleep, plus a one-time GC nudge.
56+ private static final double CGROUP_CRITICAL_WATERMARK = 0.93 ;
57+ private static final double HEAP_CRITICAL_WATERMARK = 0.90 ;
5058 private static final long SAMPLE_INTERVAL_MS = 500 ;
5159 private static final long THROTTLE_SLEEP_MS = 5 ;
60+ private static final long CRITICAL_THROTTLE_SLEEP_MS = 50 ;
5261
5362 private static final Path [] USAGE_PATHS = {
5463 Paths .get ("/sys/fs/cgroup/memory.current" ),
@@ -60,6 +69,7 @@ final class MemoryGovernor {
6069 };
6170
6271 private static volatile boolean throttled ;
72+ private static volatile boolean critical ;
6373
6474 private MemoryGovernor () {
6575 }
@@ -81,7 +91,13 @@ static void start() {
8191
8292 /** Called from an antagonist's hot allocation loop. No-op unless throttled. */
8393 static void pace () {
84- if (throttled ) {
94+ if (critical ) {
95+ try {
96+ Thread .sleep (CRITICAL_THROTTLE_SLEEP_MS );
97+ } catch (InterruptedException e ) {
98+ Thread .currentThread ().interrupt ();
99+ }
100+ } else if (throttled ) {
85101 try {
86102 Thread .sleep (THROTTLE_SLEEP_MS );
87103 } catch (InterruptedException e ) {
@@ -119,18 +135,31 @@ private static void update(Path usagePath, long limitBytes) throws IOException {
119135
120136 boolean high = cgroupFraction >= CGROUP_HIGH_WATERMARK || heapFraction >= HEAP_HIGH_WATERMARK ;
121137 boolean low = cgroupFraction <= CGROUP_LOW_WATERMARK && heapFraction <= HEAP_LOW_WATERMARK ;
138+ boolean crit = cgroupFraction >= CGROUP_CRITICAL_WATERMARK || heapFraction >= HEAP_CRITICAL_WATERMARK ;
122139 if (high && !throttled ) {
123140 throttled = true ;
124- log (cgroupFraction , heapFraction , true );
141+ log (cgroupFraction , heapFraction , "throttling" );
125142 } else if (low && throttled ) {
126143 throttled = false ;
127- log (cgroupFraction , heapFraction , false );
144+ log (cgroupFraction , heapFraction , "released" );
128145 }
129146 // else: in the dead zone between watermarks — keep current state.
147+
148+ if (crit && !critical ) {
149+ critical = true ;
150+ log (cgroupFraction , heapFraction , "critical" );
151+ // One-shot nudge on the crossing, not every sample tick, so a
152+ // sustained critical state doesn't turn into a GC storm on top
153+ // of the memory pressure it's meant to relieve.
154+ System .gc ();
155+ } else if (low && critical ) {
156+ critical = false ;
157+ log (cgroupFraction , heapFraction , "critical-released" );
158+ }
130159 }
131160
132- private static void log (double cgroupFraction , double heapFraction , boolean nowThrottled ) {
133- System .out .println ("[chaos] memory-governor " + ( nowThrottled ? "throttling" : "released" )
161+ private static void log (double cgroupFraction , double heapFraction , String state ) {
162+ System .out .println ("[chaos] memory-governor " + state
134163 + " cgroup=" + String .format ("%.2f" , cgroupFraction )
135164 + " heap=" + String .format ("%.2f" , heapFraction ));
136165 }
0 commit comments