GcDetector.java

package io.github.baokhang83.mnemo.warden.agent.heap;

import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.List;
import java.util.Locale;

/**
 * Identifies a JVM's collector from its {@link GarbageCollectorMXBean} names and maps it to
 * {@link GcCapabilities}.
 *
 * <p>The bean names are a reliable runtime fingerprint of the <em>active</em> collector (e.g.
 * {@code "ZGC Cycles"}, {@code "Shenandoah Pauses"}, {@code "G1 Young Generation"}), independent
 * of how the JVM was launched.
 *
 * <p>W-101 detects the local (agent) JVM via {@link #detectLocal()}; W-102 will point the same
 * {@link #detect(List)} logic at the attached target's beans.
 */
public final class GcDetector {

  private GcDetector() {}

  /** Detects the collector of the current JVM. */
  public static GcCapabilities detectLocal() {
    return detect(ManagementFactory.getGarbageCollectorMXBeans());
  }

  /** Detects the collector from a JVM's GC MXBeans. */
  public static GcCapabilities detect(List<GarbageCollectorMXBean> gcBeans) {
    List<String> names = gcBeans.stream().map(GarbageCollectorMXBean::getName).toList();
    return capabilitiesFor(classify(names));
  }

  /** Classifies a collector from its GC MXBean names. Package-private so it can be tested directly. */
  static Collector classify(List<String> gcBeanNames) {
    String joined = String.join(" ", gcBeanNames).toLowerCase(Locale.ROOT);
    if (joined.contains("zgc")) {
      return Collector.ZGC;
    }
    if (joined.contains("shenandoah")) {
      return Collector.SHENANDOAH;
    }
    if (joined.contains("g1")) {
      return Collector.G1;
    }
    return Collector.OTHER;
  }

  /** Maps a collector to what it lets Warden do. */
  static GcCapabilities capabilitiesFor(Collector collector) {
    return switch (collector) {
      // ZGC and Shenandoah both uncommit and honour a runtime SoftMaxHeapSize.
      case ZGC, SHENANDOAH -> new GcCapabilities(collector, true, true);
      // G1 uncommits (periodic GC) but has no runtime soft max.
      case G1 -> new GcCapabilities(Collector.G1, false, true);
      // Serial / Parallel / Epsilon / unknown: no runtime uncommit control -> read-only.
      case OTHER -> new GcCapabilities(Collector.OTHER, false, false);
    };
  }
}