SoftMax.java
package io.github.baokhang83.mnemo.warden.agent.heap;
import com.sun.management.HotSpotDiagnosticMXBean;
import io.github.baokhang83.mnemo.warden.agent.attach.AttachedJvm;
import java.io.IOException;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.List;
/**
* Reads and writes a collector's runtime soft heap ceiling ({@code SoftMaxHeapSize}) on an
* attached target — ZGC and Shenandoah both honor it (verified against a real Shenandoah
* target on the project's actual runtime image, {@code eclipse-temurin:21-jdk}, since it's the
* exact same VM option on both collectors); G1 does not.
*
* <p>Deliberately not (yet) a {@code HeapController}: {@code currentRss()} and
* {@code deepGcAndUncommit()} belong to {@link RssReader} and {@link DeepGc}. A
* {@code HeapController} implementation gets assembled once the M2 resize state machine actually
* needs the whole contract.
*
* <p>{@link #forTarget(AttachedJvm)} checks the target's real capabilities via {@link GcDetector}
* before ever touching the VM option: {@code HotSpotDiagnosticMXBean.setVMOption} succeeds
* silently on collectors that ignore {@code SoftMaxHeapSize} (G1 included, verified against a
* real target), so there is no exception to catch afterward — the guard has to come first.
*/
public final class SoftMax {
private static final String OPTION = "SoftMaxHeapSize";
private final HotSpotDiagnosticMXBean diagnostics;
private SoftMax(HotSpotDiagnosticMXBean diagnostics) {
this.diagnostics = diagnostics;
}
/**
* @throws UnsupportedCollectorException if the target's collector doesn't support a runtime
* soft max (currently: anything but ZGC and Shenandoah)
* @throws IOException if the JMX connection to the target fails
*/
public static SoftMax forTarget(AttachedJvm target) throws IOException {
List<GarbageCollectorMXBean> gcBeans =
ManagementFactory.getPlatformMXBeans(target.mbeanConnection(), GarbageCollectorMXBean.class);
GcCapabilities capabilities = GcDetector.detect(gcBeans);
if (!capabilities.supportsSoftMax()) {
throw new UnsupportedCollectorException(OPTION, capabilities.collector());
}
HotSpotDiagnosticMXBean diagnostics =
ManagementFactory.getPlatformMXBean(target.mbeanConnection(), HotSpotDiagnosticMXBean.class);
return new SoftMax(diagnostics);
}
/** The current soft heap ceiling, in bytes. */
public long softMaxHeapSize() {
return Long.parseLong(diagnostics.getVMOption(OPTION).getValue());
}
/** Sets the soft heap ceiling, in bytes. Takes effect immediately; no restart needed. */
public void setSoftMaxHeapSize(long bytes) {
diagnostics.setVMOption(OPTION, Long.toString(bytes));
}
}