From 300ecbfc8b5688b12bbfadb3c838bf5c3bf743c6 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Wed, 28 Oct 2026 13:15:35 -0400 Subject: [PATCH] Fixed container memory pressure - Container pressure excludes committed heap the JVM is not using - Reclaim, pregen gating and status text use the adjusted figure - Tests cover the committed heap subtraction --- .../iris/world/pregen/MantleHeapPressure.java | 32 +++++++++++++---- .../pregen/MantleContainerPressureTest.java | 36 +++++++++++++++++++ 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/art/arcane/iris/world/pregen/MantleHeapPressure.java b/core/src/main/java/art/arcane/iris/world/pregen/MantleHeapPressure.java index 8f7ae1478..554798af9 100644 --- a/core/src/main/java/art/arcane/iris/world/pregen/MantleHeapPressure.java +++ b/core/src/main/java/art/arcane/iris/world/pregen/MantleHeapPressure.java @@ -65,7 +65,8 @@ public final class MantleHeapPressure { } public static double usedFraction() { - return pressureFraction(heapUsedFraction(), ContainerMemory.snapshot().pressureFraction()); + MemoryUsage heap = heapUsage(); + return pressureFraction(heapUsedFraction(heap), containerPressureFraction(ContainerMemory.snapshot(), heap)); } public static long memoryBudgetBytes() { @@ -76,6 +77,14 @@ public final class MantleHeapPressure { return Math.min(heapBytes, containerBytes - containerBytes / 4L); } + static double containerPressureFraction(ContainerMemory.Snapshot container, long heapCommittedBytes, long heapUsedBytes) { + if (!container.supported()) { + return 0D; + } + long freeCommittedHeap = Math.max(0L, heapCommittedBytes - heapUsedBytes); + return Math.max(0D, container.pressureFraction() - (double) freeCommittedHeap / (double) container.limitBytes()); + } + static double pressureFraction(double heapFraction, double containerFraction) { if (containerFraction >= CONTAINER_HIGH_WATER) { return Math.max(heapFraction, HIGH_WATER + (containerFraction - CONTAINER_HIGH_WATER)); @@ -88,14 +97,22 @@ public final class MantleHeapPressure { public static String describe() { ContainerMemory.Snapshot container = ContainerMemory.snapshot(); - String heap = Math.round(heapUsedFraction() * 100D) + "% heap"; + MemoryUsage heapUsage = heapUsage(); + String heap = Math.round(heapUsedFraction(heapUsage) * 100D) + "% heap"; return container.supported() - ? heap + ", " + Math.round(container.pressureFraction() * 100D) + "% container working memory" + ? heap + ", " + Math.round(containerPressureFraction(container, heapUsage) * 100D) + "% container working memory" : heap; } - private static double heapUsedFraction() { - MemoryUsage heap = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); + private static MemoryUsage heapUsage() { + return ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); + } + + private static double containerPressureFraction(ContainerMemory.Snapshot container, MemoryUsage heap) { + return containerPressureFraction(container, heap.getCommitted(), heap.getUsed()); + } + + private static double heapUsedFraction(MemoryUsage heap) { long max = heap.getMax(); if (max <= 0L) { Runtime runtime = Runtime.getRuntime(); @@ -110,8 +127,9 @@ public final class MantleHeapPressure { } public static boolean overHighWater() { - boolean heapPressure = PRESSURE_GATE.update(heapUsedFraction()); - boolean containerPressure = CONTAINER_PRESSURE_GATE.update(ContainerMemory.snapshot().pressureFraction()); + MemoryUsage heap = heapUsage(); + boolean heapPressure = PRESSURE_GATE.update(heapUsedFraction(heap)); + boolean containerPressure = CONTAINER_PRESSURE_GATE.update(containerPressureFraction(ContainerMemory.snapshot(), heap)); return heapPressure || containerPressure; } diff --git a/core/src/test/java/art/arcane/iris/world/pregen/MantleContainerPressureTest.java b/core/src/test/java/art/arcane/iris/world/pregen/MantleContainerPressureTest.java index cf111b8dd..0dabce41b 100644 --- a/core/src/test/java/art/arcane/iris/world/pregen/MantleContainerPressureTest.java +++ b/core/src/test/java/art/arcane/iris/world/pregen/MantleContainerPressureTest.java @@ -1,5 +1,6 @@ package art.arcane.iris.world.pregen; +import art.arcane.volmlib.util.diagnostics.ContainerMemory; import org.junit.Test; import java.util.concurrent.atomic.AtomicLong; @@ -9,6 +10,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class MantleContainerPressureTest { + private static final long MIB = 1L << 20; + @Test public void containerPressureReclaimsEvenWhenTheHeapHasHeadroom() { double pressure = MantleHeapPressure.pressureFraction(0.45D, 0.90D); @@ -39,4 +42,37 @@ public class MantleContainerPressureTest { assertTrue(gate.update(0.85D)); assertFalse(gate.update(0.80D)); } + + @Test + public void freeCommittedHeapDoesNotCountAsContainerPressure() { + ContainerMemory.Snapshot container = containerAt(8_601L * MIB, 8_240L * MIB); + double containerPressure = MantleHeapPressure.containerPressureFraction(container, 7_388L * MIB, 2_355L * MIB); + assertEquals((8_240D - 5_033D) / 8_601D, containerPressure, 1E-9D); + assertEquals(0D, MantleHeapPressure.reclaimUrgency(MantleHeapPressure.pressureFraction(0.31D, containerPressure)), 0D); + } + + @Test + public void heapFillingItsCommittedSpaceStillRaisesContainerPressure() { + ContainerMemory.Snapshot container = containerAt(8_601L * MIB, 8_240L * MIB); + double containerPressure = MantleHeapPressure.containerPressureFraction(container, 7_388L * MIB, 7_200L * MIB); + assertTrue(containerPressure >= 0.90D); + assertEquals(1D, MantleHeapPressure.reclaimUrgency(MantleHeapPressure.pressureFraction(0.94D, containerPressure)), 0D); + } + + @Test + public void containerPressureNeverDropsBelowZero() { + ContainerMemory.Snapshot container = containerAt(4_096L * MIB, 1_024L * MIB); + assertEquals(0D, MantleHeapPressure.containerPressureFraction(container, 3_072L * MIB, 512L * MIB), 0D); + } + + @Test + public void unlimitedContainersReportNoContainerPressure() { + ContainerMemory.Snapshot container = new ContainerMemory.Snapshot(Long.MAX_VALUE, 0L, 0L, 0D, Long.MAX_VALUE); + assertEquals(0D, MantleHeapPressure.containerPressureFraction(container, 7_388L * MIB, 2_355L * MIB), 0D); + } + + private static ContainerMemory.Snapshot containerAt(long limitBytes, long workingSetBytes) { + return new ContainerMemory.Snapshot(limitBytes, workingSetBytes, workingSetBytes, + workingSetBytes / (double) limitBytes, limitBytes - workingSetBytes); + } }