@Override
  public void bindTo(AbstractMetricService metricService) {
    if (!preCheck()) {
      return;
    }

    double maxLongLivedPoolBytes =
        ManagementFactory.getPlatformMXBeans(MemoryPoolMXBean.class).stream()
            .filter(mem -> MemoryType.HEAP.equals(mem.getType()))
            .filter(mem -> isOldGenPool(mem.getName()) || isNonGenerationalHeapPool(mem.getName()))
            .findAny()
            .map(mem -> JvmUtils.getUsageValue(mem, MemoryUsage::getMax))
            .orElse(0.0);

    AtomicLong maxDataSize = new AtomicLong((long) maxLongLivedPoolBytes);
    metricService.createAutoGauge(
        SystemMetric.JVM_GC_MAX_DATA_SIZE_BYTES.toString(),
        MetricLevel.CORE,
        maxDataSize,
        AtomicLong::get);

    AtomicLong liveDataSize = new AtomicLong();
    metricService.createAutoGauge(
        SystemMetric.JVM_GC_LIVE_DATA_SIZE_BYTES.toString(),
        MetricLevel.CORE,
        liveDataSize,
        AtomicLong::get);

    Counter promotedBytes =
        (oldGenPoolName == null)
            ? null
            : metricService.getOrCreateCounter(
                SystemMetric.JVM_GC_MEMORY_PROMOTED_BYTES.toString(), MetricLevel.CORE);

    Counter nonGenAllocatedBytes =
        (nonGenerationalMemoryPool == null)
            ? null
            : metricService.getOrCreateCounter(
                SystemMetric.JVM_GC_NON_GEN_MEMORY_ALLOCATED_BYTES.toString(), MetricLevel.CORE);

    Counter oldGenAllocatedBytes =
        (oldGenPoolName == null)
            ? null
            : metricService.getOrCreateCounter(
                SystemMetric.JVM_GC_OLD_MEMORY_ALLOCATED_BYTES.toString(), MetricLevel.CORE);

    Counter youngGenAllocatedBytes =
        (firstYoungGenPoolName == null)
            ? null
            : metricService.getOrCreateCounter(
                SystemMetric.JVM_GC_YOUNG_MEMORY_ALLOCATED_BYTES.toString(), MetricLevel.CORE);

    final AtomicLong firstYoungHeapPoolSizeAfterGc = new AtomicLong();
    // long live heap pool includes old gen heap pool and non-generation heap pool.
    final AtomicLong longLivedHeapPoolSizeAfterGc = new AtomicLong();

    // start watching for GC notifications
    for (GarbageCollectorMXBean mbean : ManagementFactory.getGarbageCollectorMXBeans()) {
      if (!(mbean instanceof NotificationEmitter)) {
        continue;
      }
      NotificationListener notificationListener =
          (notification, ref) -> {
            CompositeData cd = (CompositeData) notification.getUserData();
            GarbageCollectionNotificationInfo notificationInfo =
                GarbageCollectionNotificationInfo.from(cd);

            String gcCause = notificationInfo.getGcCause();
            String gcAction = notificationInfo.getGcAction();
            GcInfo gcInfo = notificationInfo.getGcInfo();

            // The duration supplied in the notification info includes more than just
            // application stopped time for concurrent GCs (since the concurrent phase is not
            // stop-the-world).
            // E.g. For mixed GC or full GC in collector 'G1 old generation', the duration collected
            // here is more than the actual pause time (the latter can be accessed by GC
            // log/-XX:PrintGCDetails)
            long duration = gcInfo.getDuration();

            // Try and do a better job coming up with a good stopped time
            // value by asking for and tracking cumulative time spent blocked in GC.
            if (isPartiallyConcurrentGC(mbean)) {
              AtomicLong previousTotal =
                  lastGcTotalDurationMap.computeIfAbsent(mbean.getName(), k -> new AtomicLong());
              long total = mbean.getCollectionTime();
              duration = total - previousTotal.get(); // may be zero for a really fast collection
              previousTotal.set(total);
            }

            // create a timer with tags named by gcCause, which binds gcCause with gcDuration
            Timer timer =
                metricService.getOrCreateTimer(
                    SystemMetric.JVM_GC_PAUSE.toString(),
                    MetricLevel.CORE,
                    "action",
                    gcAction,
                    "cause",
                    gcCause);
            timer.update(duration, TimeUnit.MILLISECONDS);

            // add support for ZGC
            if (mbean.getName().equals("ZGC Cycles")) {
              Counter cyclesCount =
                  metricService.getOrCreateCounter(
                      SystemMetric.JVM_ZGC_CYCLES_COUNT.toString(), MetricLevel.CORE);
              cyclesCount.inc();
            } else if (mbean.getName().equals("ZGC Pauses")) {
              Counter pausesCount =
                  metricService.getOrCreateCounter(
                      SystemMetric.JVM_ZGC_PAUSES_COUNT.toString(), MetricLevel.CORE);
              pausesCount.inc();
            }

            // Update promotion and allocation counters
            final Map<String, MemoryUsage> before = gcInfo.getMemoryUsageBeforeGc();
            final Map<String, MemoryUsage> after = gcInfo.getMemoryUsageAfterGc();

            if (nonGenerationalMemoryPool != null) {
              countPoolSizeDelta(
                  gcInfo.getMemoryUsageBeforeGc(),
                  gcInfo.getMemoryUsageAfterGc(),
                  nonGenAllocatedBytes,
                  longLivedHeapPoolSizeAfterGc,
                  nonGenerationalMemoryPool);
              if (after.get(nonGenerationalMemoryPool).getUsed()
                  < before.get(nonGenerationalMemoryPool).getUsed()) {
                liveDataSize.set(after.get(nonGenerationalMemoryPool).getUsed());
                final long longLivedMaxAfter = after.get(nonGenerationalMemoryPool).getMax();
                maxDataSize.set(longLivedMaxAfter);
              }
            }

            // should add `else` here, since there are only two
            // cases: generational and non-generational
            else {
              if (oldGenPoolName != null) {
                final long oldBefore = before.get(oldGenPoolName).getUsed();
                final long oldAfter = after.get(oldGenPoolName).getUsed();
                final long delta = oldAfter - oldBefore;
                if (delta > 0L && promotedBytes != null) {
                  promotedBytes.inc(delta);
                }

                // Some GC implementations such as G1 can reduce the old gen size as part of a minor
                // GC (since in JMX, a minor GC of G1 may actually represent mixed GC, which collect
                // some obj in old gen region). To track the
                // live data size we record the value if we see a reduction in the old gen heap size
                // or after a major GC.
                if (oldAfter < oldBefore
                    || GcGenerationAge.fromName(notificationInfo.getGcName())
                        == GcGenerationAge.OLD) {
                  liveDataSize.set(oldAfter);
                  final long oldMaxAfter = after.get(oldGenPoolName).getMax();
                  maxDataSize.set(oldMaxAfter);
                }
                countPoolSizeDelta(
                    gcInfo.getMemoryUsageBeforeGc(),
                    gcInfo.getMemoryUsageAfterGc(),
                    oldGenAllocatedBytes,
                    longLivedHeapPoolSizeAfterGc,
                    oldGenPoolName);
              }

              if (firstYoungGenPoolName != null) {
                countPoolSizeDelta(
                    gcInfo.getMemoryUsageBeforeGc(),
                    gcInfo.getMemoryUsageAfterGc(),
                    youngGenAllocatedBytes,
                    firstYoungHeapPoolSizeAfterGc,
                    firstYoungGenPoolName);
              }
            }
          };
      NotificationEmitter notificationEmitter = (NotificationEmitter) mbean;
      notificationEmitter.addNotificationListener(
          notificationListener,
          notification ->
              notification
                  .getType()
                  .equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION),
          null);
      notificationListenerCleanUpRunnables.add(
          () -> {
            try {
              notificationEmitter.removeNotificationListener(notificationListener);
            } catch (ListenerNotFoundException ignore) {
              // do nothing
            }
          });
    }
  }
This file is modified from io.micrometer.core.instrument.binder.jvm.JvmGcMetrics
iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/metricsets/jvm/JvmGcMetrics.java