diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 677ef89796ce..9ef89209542e 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1029,6 +1029,9 @@ "jdk.internal.misc", "sun.security.jca", ], + "java.management": [ + "sun.management", + ], }, "checkstyle": "com.oracle.svm.test", "checkstyleVersion" : "10.7.0", diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CompleteGarbageCollectorMXBean.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CompleteGarbageCollectorMXBean.java index c94f2f39d819..b41d91b7c0d3 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CompleteGarbageCollectorMXBean.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CompleteGarbageCollectorMXBean.java @@ -26,21 +26,18 @@ import java.lang.management.ManagementFactory; -import javax.management.MBeanNotificationInfo; -import javax.management.NotificationEmitter; -import javax.management.NotificationFilter; -import javax.management.NotificationListener; import javax.management.ObjectName; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.gc.AbstractGarbageCollectorMXBean; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.util.TimeUtils; -import com.sun.management.GcInfo; import sun.management.Util; -public final class CompleteGarbageCollectorMXBean implements com.sun.management.GarbageCollectorMXBean, NotificationEmitter { +public final class CompleteGarbageCollectorMXBean extends AbstractGarbageCollectorMXBean { @Platforms(Platform.HOSTED_ONLY.class) public CompleteGarbageCollectorMXBean() { @@ -83,24 +80,13 @@ public ObjectName getObjectName() { } @Override - public void removeNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) { + protected int gcThreadCount() { + return 1; } @Override - public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) { - } - - @Override - public void removeNotificationListener(NotificationListener listener) { - } - - @Override - public MBeanNotificationInfo[] getNotificationInfo() { - return new MBeanNotificationInfo[0]; - } - - @Override - public GcInfo getLastGcInfo() { - return null; + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean isIncremental() { + return false; } } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java index df8ca685efbb..c56787c09995 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java @@ -60,12 +60,15 @@ import com.oracle.svm.core.deopt.DeoptimizationSlotPacking; import com.oracle.svm.core.deopt.DeoptimizedFrame; import com.oracle.svm.core.deopt.Deoptimizer; +import com.oracle.svm.core.gc.MemoryPoolMXBeansProvider; import com.oracle.svm.core.genscavenge.AlignedHeapChunk.AlignedHeader; import com.oracle.svm.core.genscavenge.BasicCollectionPolicies.NeverCollect; import com.oracle.svm.core.genscavenge.HeapAccounting.HeapSizes; import com.oracle.svm.core.genscavenge.HeapChunk.Header; import com.oracle.svm.core.genscavenge.UnalignedHeapChunk.UnalignedHeader; import com.oracle.svm.core.genscavenge.remset.RememberedSet; +import com.oracle.svm.core.notification.GcNotifier; +import com.oracle.svm.core.notification.HasGcNotificationSupport; import com.oracle.svm.core.graal.RuntimeCompilation; import com.oracle.svm.core.heap.CodeReferenceMapDecoder; import com.oracle.svm.core.heap.GC; @@ -239,7 +242,10 @@ assert getCollectionEpoch().equal(data.getRequestingEpoch()) || Timer collectionTimer = timers.collection.open(); try { ThreadLocalAllocation.disableAndFlushForAllThreads(); - GenScavengeMemoryPoolMXBeans.singleton().notifyBeforeCollection(); + MemoryPoolMXBeansProvider.get().notifyBeforeCollection(); + if (HasGcNotificationSupport.get()) { + GcNotifier.singleton().beforeCollection(TimeUtils.roundNanosToMillis(collectionTimer.getOpenedTime() - Isolates.getCurrentStartNanoTime())); + } HeapImpl.getAccounting().notifyBeforeCollection(); verifyHeap(Before); @@ -254,9 +260,14 @@ assert getCollectionEpoch().equal(data.getRequestingEpoch()) || accounting.updateCollectionCountAndTime(completeCollection, collectionTimer.getMeasuredNanos()); HeapImpl.getAccounting().notifyAfterCollection(); - GenScavengeMemoryPoolMXBeans.singleton().notifyAfterCollection(); + MemoryPoolMXBeansProvider.get().notifyAfterCollection(); ChunkBasedCommittedMemoryProvider.get().afterGarbageCollection(); + if (HasGcNotificationSupport.get()) { + GcNotifier.singleton().afterCollection(!completeCollection, cause, getCollectionEpoch().rawValue(), + TimeUtils.roundNanosToMillis(collectionTimer.getClosedTime() - Isolates.getCurrentStartNanoTime())); + } + printGCAfter(cause); JfrGCHeapSummaryEvent.emit(JfrGCWhen.AFTER_GC); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GenScavengeMemoryPoolMXBeans.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GenScavengeMemoryPoolMXBeans.java index 093e7e9e3796..dc96af5ccffd 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GenScavengeMemoryPoolMXBeans.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GenScavengeMemoryPoolMXBeans.java @@ -27,7 +27,8 @@ import java.lang.management.MemoryUsage; -import org.graalvm.nativeimage.ImageSingletons; +import com.oracle.svm.core.gc.AbstractMemoryPoolMXBean; +import com.oracle.svm.core.gc.MemoryPoolMXBeansProvider; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; @@ -36,11 +37,9 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.util.VMError; -import jdk.graal.compiler.api.replacements.Fold; - -public class GenScavengeMemoryPoolMXBeans { - static final String YOUNG_GEN_SCAVENGER = "young generation scavenger"; - static final String COMPLETE_SCAVENGER = "complete scavenger"; +public class GenScavengeMemoryPoolMXBeans implements MemoryPoolMXBeansProvider { + public static final String YOUNG_GEN_SCAVENGER = "young generation scavenger"; + public static final String COMPLETE_SCAVENGER = "complete scavenger"; static final String EPSILON_SCAVENGER = "epsilon scavenger"; static final String EDEN_SPACE = "eden space"; @@ -66,21 +65,19 @@ public GenScavengeMemoryPoolMXBeans() { } } - @Fold - public static GenScavengeMemoryPoolMXBeans singleton() { - return ImageSingletons.lookup(GenScavengeMemoryPoolMXBeans.class); - } - + @Override public AbstractMemoryPoolMXBean[] getMXBeans() { return mxBeans; } + @Override public void notifyBeforeCollection() { for (AbstractMemoryPoolMXBean mxBean : mxBeans) { mxBean.beforeCollection(); } } + @Override public void notifyAfterCollection() { for (AbstractMemoryPoolMXBean mxBean : mxBeans) { mxBean.afterCollection(); @@ -95,28 +92,28 @@ static final class EdenMemoryPoolMXBean extends AbstractMemoryPoolMXBean { } @Override - void beforeCollection() { + public void beforeCollection() { updatePeakUsage(HeapImpl.getAccounting().getEdenUsedBytes()); } @Override - void afterCollection() { + public void afterCollection() { /* Nothing to do. */ } @Override - UnsignedWord computeInitialValue() { + protected UnsignedWord computeInitialValue() { return GCImpl.getPolicy().getInitialEdenSize(); } @Override public MemoryUsage getUsage() { - return memoryUsage(getCurrentUsage()); + return memoryUsage(getUsedBytes()); } @Override public MemoryUsage getPeakUsage() { - updatePeakUsage(getCurrentUsage()); + updatePeakUsage(getUsedBytes()); return memoryUsage(peakUsage.get()); } @@ -125,9 +122,15 @@ public MemoryUsage getCollectionUsage() { return memoryUsage(WordFactory.zero()); } - private static UnsignedWord getCurrentUsage() { + @Override + public UnsignedWord getUsedBytes() { return HeapImpl.getAccounting().getEdenUsedBytes(); } + + @Override + public UnsignedWord getCommittedBytes() { + return HeapImpl.getAccounting().getEdenUsedBytes().add(HeapImpl.getAccounting().getBytesInUnusedChunks()); + } } static final class SurvivorMemoryPoolMXBean extends AbstractMemoryPoolMXBean { @@ -138,17 +141,17 @@ static final class SurvivorMemoryPoolMXBean extends AbstractMemoryPoolMXBean { } @Override - void beforeCollection() { + public void beforeCollection() { /* Nothing to do. */ } @Override - void afterCollection() { + public void afterCollection() { updatePeakUsage(HeapImpl.getAccounting().getSurvivorUsedBytes()); } @Override - UnsignedWord computeInitialValue() { + protected UnsignedWord computeInitialValue() { return GCImpl.getPolicy().getInitialSurvivorSize(); } @@ -166,6 +169,11 @@ public MemoryUsage getPeakUsage() { public MemoryUsage getCollectionUsage() { return memoryUsage(HeapImpl.getAccounting().getSurvivorUsedBytes()); } + + @Override + public UnsignedWord getUsedBytes() { + return HeapImpl.getAccounting().getSurvivorUsedBytes(); + } } static final class OldGenerationMemoryPoolMXBean extends AbstractMemoryPoolMXBean { @@ -176,17 +184,17 @@ static final class OldGenerationMemoryPoolMXBean extends AbstractMemoryPoolMXBea } @Override - void beforeCollection() { + public void beforeCollection() { /* Nothing to do. */ } @Override - void afterCollection() { + public void afterCollection() { updatePeakUsage(HeapImpl.getAccounting().getOldUsedBytes()); } @Override - UnsignedWord computeInitialValue() { + protected UnsignedWord computeInitialValue() { return GCImpl.getPolicy().getInitialOldSize(); } @@ -204,6 +212,11 @@ public MemoryUsage getPeakUsage() { public MemoryUsage getCollectionUsage() { return memoryUsage(HeapImpl.getAccounting().getOldUsedBytes()); } + + @Override + public UnsignedWord getUsedBytes() { + return HeapImpl.getAccounting().getOldUsedBytes(); + } } static final class EpsilonMemoryPoolMXBean extends AbstractMemoryPoolMXBean { @@ -214,17 +227,17 @@ static final class EpsilonMemoryPoolMXBean extends AbstractMemoryPoolMXBean { } @Override - void beforeCollection() { + public void beforeCollection() { throw VMError.shouldNotReachHereAtRuntime(); // ExcludeFromJacocoGeneratedReport } @Override - void afterCollection() { + public void afterCollection() { throw VMError.shouldNotReachHereAtRuntime(); // ExcludeFromJacocoGeneratedReport } @Override - UnsignedWord computeInitialValue() { + protected UnsignedWord computeInitialValue() { return GCImpl.getPolicy().getMinimumHeapSize(); } @@ -243,5 +256,15 @@ public MemoryUsage getPeakUsage() { public MemoryUsage getCollectionUsage() { return memoryUsage(WordFactory.zero()); } + + @Override + public UnsignedWord getUsedBytes() { + return HeapImpl.getAccounting().getUsedBytes(); + } + + @Override + public UnsignedWord getCommittedBytes() { + return HeapImpl.getAccounting().getCommittedBytes(); + } } } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/IncrementalGarbageCollectorMXBean.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/IncrementalGarbageCollectorMXBean.java index 38f7305c177f..03b2b6a8d1bd 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/IncrementalGarbageCollectorMXBean.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/IncrementalGarbageCollectorMXBean.java @@ -26,21 +26,18 @@ import java.lang.management.ManagementFactory; -import javax.management.MBeanNotificationInfo; -import javax.management.NotificationEmitter; -import javax.management.NotificationFilter; -import javax.management.NotificationListener; import javax.management.ObjectName; +import com.oracle.svm.core.Uninterruptible; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.core.gc.AbstractGarbageCollectorMXBean; import com.oracle.svm.core.util.TimeUtils; -import com.sun.management.GcInfo; import sun.management.Util; -public final class IncrementalGarbageCollectorMXBean implements com.sun.management.GarbageCollectorMXBean, NotificationEmitter { +public final class IncrementalGarbageCollectorMXBean extends AbstractGarbageCollectorMXBean { @Platforms(Platform.HOSTED_ONLY.class) public IncrementalGarbageCollectorMXBean() { @@ -82,24 +79,13 @@ public ObjectName getObjectName() { } @Override - public void removeNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) { + protected int gcThreadCount() { + return 1; } @Override - public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) { - } - - @Override - public void removeNotificationListener(NotificationListener listener) { - } - - @Override - public MBeanNotificationInfo[] getNotificationInfo() { - return new MBeanNotificationInfo[0]; - } - - @Override - public GcInfo getLastGcInfo() { - return null; + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean isIncremental() { + return true; } } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeGCFeature.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeGCFeature.java index fe67125edde0..c2e2cb04ce54 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeGCFeature.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/graal/GenScavengeGCFeature.java @@ -36,6 +36,7 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.gc.MemoryPoolMXBeansProvider; import com.oracle.svm.core.genscavenge.ChunkedImageHeapLayouter; import com.oracle.svm.core.genscavenge.GenScavengeMemoryPoolMXBeans; import com.oracle.svm.core.genscavenge.HeapImpl; @@ -84,7 +85,7 @@ public void afterRegistration(AfterRegistrationAccess access) { ImageSingletons.add(BarrierSetProvider.class, rememberedSet); GenScavengeMemoryPoolMXBeans memoryPoolMXBeans = new GenScavengeMemoryPoolMXBeans(); - ImageSingletons.add(GenScavengeMemoryPoolMXBeans.class, memoryPoolMXBeans); + ImageSingletons.add(MemoryPoolMXBeansProvider.class, memoryPoolMXBeans); ImageSingletons.add(GCRelatedMXBeans.class, new GenScavengeRelatedMXBeans(memoryPoolMXBeans)); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java index 27e7fbd6ff29..55a45cb5e5bc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java @@ -60,12 +60,14 @@ public final class VMInspectionOptions { private static final String MONITORING_JMXSERVER_NAME = "jmxserver"; private static final String MONITORING_THREADDUMP_NAME = "threaddump"; private static final String MONITORING_NMT_NAME = "nmt"; + private static final String MONITORING_GC_NOTIFICATIONS = "gcnotif"; private static final List MONITORING_ALL_VALUES = List.of(MONITORING_HEAPDUMP_NAME, MONITORING_JFR_NAME, MONITORING_JVMSTAT_NAME, MONITORING_JMXCLIENT_NAME, MONITORING_JMXSERVER_NAME, - MONITORING_THREADDUMP_NAME, MONITORING_NMT_NAME, MONITORING_ALL_NAME, MONITORING_DEFAULT_NAME); + MONITORING_THREADDUMP_NAME, MONITORING_NMT_NAME, MONITORING_GC_NOTIFICATIONS, MONITORING_ALL_NAME, MONITORING_DEFAULT_NAME); private static final List NOT_SUPPORTED_ON_WINDOWS = List.of(MONITORING_HEAPDUMP_NAME, MONITORING_JFR_NAME, MONITORING_JVMSTAT_NAME, MONITORING_JMXCLIENT_NAME, MONITORING_JMXSERVER_NAME); private static final String MONITORING_ALLOWED_VALUES_TEXT = "'" + MONITORING_HEAPDUMP_NAME + "', '" + MONITORING_JFR_NAME + "', '" + MONITORING_JVMSTAT_NAME + "', '" + MONITORING_JMXSERVER_NAME + - "' (experimental), '" + MONITORING_JMXCLIENT_NAME + "' (experimental), '" + MONITORING_THREADDUMP_NAME + "', '" + MONITORING_NMT_NAME + "' (experimental), or '" + + "' (experimental), '" + MONITORING_JMXCLIENT_NAME + "' (experimental), '" + MONITORING_THREADDUMP_NAME + "', '" + MONITORING_GC_NOTIFICATIONS + "', '" + MONITORING_NMT_NAME + + "' (experimental), or '" + MONITORING_ALL_NAME + "' (deprecated behavior: defaults to '" + MONITORING_ALL_NAME + "' if no argument is provided)"; static { @@ -190,6 +192,11 @@ public static boolean hasNativeMemoryTrackingSupport() { return hasAllOrKeywordMonitoringSupport(MONITORING_NMT_NAME); } + @Fold + public static boolean hasGcNotificationSupport() { + return hasAllOrKeywordMonitoringSupport(MONITORING_GC_NOTIFICATIONS); + } + static class DeprecatedOptions { @Option(help = "Enables features that allow the VM to be inspected during run time.", type = OptionType.User, // deprecated = true, deprecationMessage = "Please use '--" + ENABLE_MONITORING_OPTION + "'") // diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/CircularQueue.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/CircularQueue.java new file mode 100644 index 000000000000..3a77af025419 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/CircularQueue.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.collections; + +import com.oracle.svm.core.Uninterruptible; + +import java.util.function.Supplier; + +/** Keeps the last n-1 entries. Entries at the head are removed first. */ +public final class CircularQueue extends RingBuffer { + private int head; + + public CircularQueue(int numEntries, Supplier supplier) { + super(numEntries, supplier); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void advanceHead() { + head = nextIndex(head); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Override + public void advance() { + super.advance(); + // If rear is now lapping front, then increment front to prevent overlap. + if (head == pos) { + head = nextIndex(head); + } + } + + public T peekTail() { + return entries[pos]; + } + + public T dequeue() { + T result = entries[head]; + advanceHead(); + return result; + } + + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public T replaceHead(T newHead) { + T oldHead = entries[head]; + entries[head] = newHead; + return oldHead; + } + + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean isEmpty() { + return pos == head; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/RingBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/RingBuffer.java index 8cd6b4df17f8..244a0379082d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/RingBuffer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/RingBuffer.java @@ -29,9 +29,9 @@ import com.oracle.svm.core.Uninterruptible; /** Keeps the last-n entries. */ -public final class RingBuffer { - private final T[] entries; - private int pos; +public class RingBuffer { + protected final T[] entries; + protected int pos; private boolean wrapped; public interface Consumer { @@ -56,7 +56,7 @@ public int size() { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private int nextIndex(int p) { + protected int nextIndex(int p) { return (p + 1) % entries.length; } @@ -67,7 +67,7 @@ public void append(T entry) { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void advance() { + protected void advance() { int posNext = nextIndex(pos); if (posNext <= pos) { wrapped = true; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/gc/AbstractGarbageCollectorMXBean.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/gc/AbstractGarbageCollectorMXBean.java new file mode 100644 index 000000000000..b6cdc2c9e700 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/gc/AbstractGarbageCollectorMXBean.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.gc; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.util.BasedOnJDKFile; + +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.notification.GcNotifier; +import com.oracle.svm.core.notification.GcNotificationRequest; +import com.oracle.svm.core.notification.HasGcNotificationSupport; +import com.oracle.svm.core.notification.PoolMemoryUsage; +import com.oracle.svm.core.notification.Target_com_sun_management_GcInfo; +import com.oracle.svm.core.notification.Target_com_sun_management_internal_GcInfoBuilder; + +import com.sun.management.GarbageCollectionNotificationInfo; +import com.sun.management.GarbageCollectorMXBean; +import com.sun.management.GcInfo; +import com.sun.management.internal.GarbageCollectionNotifInfoCompositeData; +import sun.management.NotificationEmitterSupport; + +import javax.management.MBeanNotificationInfo; +import javax.management.Notification; +import javax.management.NotificationEmitter; +import javax.management.openmbean.CompositeData; +import java.lang.management.MemoryUsage; + +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+19/src/jdk.management/share/classes/com/sun/management/internal/GarbageCollectorExtImpl.java") // +public abstract class AbstractGarbageCollectorMXBean extends NotificationEmitterSupport + implements GarbageCollectorMXBean, NotificationEmitter { + + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+19/src/hotspot/share/gc/serial/serialHeap.cpp#L451") // + private static final String ACTION_MINOR = "end of minor GC"; + + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+19/src/hotspot/share/gc/serial/serialHeap.cpp#L718") // + private static final String ACTION_MAJOR = "end of major GC"; + + private Target_com_sun_management_internal_GcInfoBuilder gcInfoBuilder; + private long seqNumber = 0; + + /** + * Use the data taken from the request queue to populate MemoryUsage. The notification thread + * calls this method. + */ + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+19/src/jdk.management/share/classes/com/sun/management/internal/GarbageCollectorExtImpl.java#L93-L116") // + public void createNotification(GcNotificationRequest request) { + if (!hasListeners()) { + return; + } + + GcInfo gcInfo = getGcInfoFromRequest(request); + + Notification notif = new Notification(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION, + getObjectName(), + getNextSeqNumber(), + request.getTimestamp(), + getName()); + + GarbageCollectionNotificationInfo info = new GarbageCollectionNotificationInfo( + getName(), + request.isIncremental() ? ACTION_MINOR : ACTION_MAJOR, + request.getCause().getName(), + gcInfo); + + CompositeData cd = GarbageCollectionNotifInfoCompositeData.toCompositeData(info); + notif.setUserData(cd); + sendNotification(notif); + } + + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+19/src/jdk.management/share/classes/com/sun/management/internal/GarbageCollectorExtImpl.java#L80-L86") // + @Override + public MBeanNotificationInfo[] getNotificationInfo() { + if (!HasGcNotificationSupport.get()) { + return new MBeanNotificationInfo[0]; + } + return new MBeanNotificationInfo[]{ + new MBeanNotificationInfo( + new String[]{GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION}, + "javax.management.Notification", + "GC Notification") + }; + } + + @Override + public GcInfo getLastGcInfo() { + if (!HasGcNotificationSupport.get()) { + return null; + } + GcNotificationRequest request = new GcNotificationRequest(); + getLastGcInfo(request); + if (request.getEpoch() < 0) { + // This collector has not done any collections yet. + return null; + } + return getGcInfoFromRequest(request); + } + + @Uninterruptible(reason = "Avoid potential races with GC.") + private void getLastGcInfo(GcNotificationRequest request) { + GcNotifier.singleton().getLatestRequest(isIncremental()).copyTo(request); + } + + private GcInfo getGcInfoFromRequest(GcNotificationRequest request) { + AbstractMemoryPoolMXBean[] beans = MemoryPoolMXBeansProvider.get().getMXBeans(); + + String[] poolNames = getMemoryPoolNames(); + MemoryUsage[] before = new MemoryUsage[poolNames.length]; + MemoryUsage[] after = new MemoryUsage[poolNames.length]; + + // Pools must be in the order of getMemoryPoolNames() to match GcInfoBuilder + for (int i = 0; i < poolNames.length; i++) { + for (int j = 0; j < beans.length; j++) { + PoolMemoryUsage pmu = request.getPoolBefore(j); + if (pmu.getName() != null && pmu.getName().equals(poolNames[i])) { + before[i] = beans[j].memoryUsage(pmu.getUsed(), pmu.getCommitted()); + pmu = request.getPoolAfter(j); + after[i] = beans[j].memoryUsage(pmu.getUsed(), pmu.getCommitted()); + } + } + assert before[i] != null && after[i] != null; + } + + Object[] extAttribute = new Object[1]; + extAttribute[0] = gcThreadCount(); + + Target_com_sun_management_GcInfo targetGcInfo = new Target_com_sun_management_GcInfo(getGcInfoBuilder(), request.getEpoch(), request.getStartTime(), request.getEndTime(), before, after, + extAttribute); + return SubstrateUtil.cast(targetGcInfo, GcInfo.class); + } + + private Target_com_sun_management_internal_GcInfoBuilder getGcInfoBuilder() { + if (gcInfoBuilder == null) { + gcInfoBuilder = new Target_com_sun_management_internal_GcInfoBuilder(this, getMemoryPoolNames()); + } + return gcInfoBuilder; + } + + private long getNextSeqNumber() { + return ++seqNumber; + } + + protected abstract int gcThreadCount(); + + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public abstract boolean isIncremental(); +} diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractMemoryPoolMXBean.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/gc/AbstractMemoryPoolMXBean.java similarity index 87% rename from substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractMemoryPoolMXBean.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/gc/AbstractMemoryPoolMXBean.java index d0724fd7b597..589a625cc2d7 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractMemoryPoolMXBean.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/gc/AbstractMemoryPoolMXBean.java @@ -23,7 +23,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.core.genscavenge; +package com.oracle.svm.core.gc; import java.lang.management.ManagementFactory; import java.lang.management.MemoryPoolMXBean; @@ -66,18 +66,28 @@ UnsignedWord getInitialValue() { return initialValue; } - abstract UnsignedWord computeInitialValue(); + public abstract UnsignedWord getUsedBytes(); - abstract void beforeCollection(); + public UnsignedWord getCommittedBytes() { + return getUsedBytes(); + } + + protected abstract UnsignedWord computeInitialValue(); + + public abstract void beforeCollection(); - abstract void afterCollection(); + public abstract void afterCollection(); + + protected MemoryUsage memoryUsage(UnsignedWord usedAndCommitted) { + return memoryUsage(usedAndCommitted, usedAndCommitted); + } - MemoryUsage memoryUsage(UnsignedWord usedAndCommitted) { - return new MemoryUsage(getInitialValue().rawValue(), usedAndCommitted.rawValue(), usedAndCommitted.rawValue(), getMaximumValue().rawValue()); + protected MemoryUsage memoryUsage(UnsignedWord used, UnsignedWord committed) { + return memoryUsage(used.rawValue(), committed.rawValue()); } - MemoryUsage memoryUsage(UnsignedWord used, UnsignedWord committed) { - return new MemoryUsage(getInitialValue().rawValue(), used.rawValue(), committed.rawValue(), getMaximumValue().rawValue()); + public MemoryUsage memoryUsage(long used, long committed) { + return new MemoryUsage(getInitialValue().rawValue(), used, committed, getMaximumValue().rawValue()); } @Override @@ -160,7 +170,7 @@ public void resetPeakUsage() { peakUsage.set(WordFactory.zero()); } - void updatePeakUsage(UnsignedWord value) { + protected void updatePeakUsage(UnsignedWord value) { UnsignedWord current; do { current = peakUsage.get(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/gc/MemoryPoolMXBeansProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/gc/MemoryPoolMXBeansProvider.java new file mode 100644 index 000000000000..787e23ecaf3f --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/gc/MemoryPoolMXBeansProvider.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.gc; + +import jdk.graal.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; + +public interface MemoryPoolMXBeansProvider { + @Fold + static MemoryPoolMXBeansProvider get() { + return ImageSingletons.lookup(MemoryPoolMXBeansProvider.class); + } + + AbstractMemoryPoolMXBean[] getMXBeans(); + + void notifyBeforeCollection(); + + void notifyAfterCollection(); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/GcNotificationFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/GcNotificationFeature.java new file mode 100644 index 000000000000..cb5fb2cfc51c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/GcNotificationFeature.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.notification; + +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import org.graalvm.nativeimage.ImageSingletons; + +@AutomaticallyRegisteredFeature +public class GcNotificationFeature implements InternalFeature { + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return HasGcNotificationSupport.get(); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + ImageSingletons.add(GcNotifier.class, new GcNotifier()); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/GcNotificationRequest.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/GcNotificationRequest.java new file mode 100644 index 000000000000..0c7b0167ded0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/GcNotificationRequest.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.notification; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.gc.AbstractMemoryPoolMXBean; +import com.oracle.svm.core.gc.MemoryPoolMXBeansProvider; +import com.oracle.svm.core.heap.GCCause; + +/** + * This class stashes GC info during a safepoint until it can be dequeued to emit a notification at + * a point in the future. + */ +public class GcNotificationRequest { + private final PoolMemoryUsage[] before; + private final PoolMemoryUsage[] after; + + // Times since the VM started. + private long startTime; + private long endTime; + + // This is the system time that the request was sent. + private long timestamp; + private boolean isIncremental; + private GCCause cause; + private long epoch; + + public GcNotificationRequest() { + int poolCount = MemoryPoolMXBeansProvider.get().getMXBeans().length; + before = new PoolMemoryUsage[poolCount]; + after = new PoolMemoryUsage[poolCount]; + for (int i = 0; i < poolCount; i++) { + before[i] = new PoolMemoryUsage(); + after[i] = new PoolMemoryUsage(); + } + epoch = -1; // Indicate no collections happened yet. + } + + @SuppressWarnings("hiding") + public void beforeCollection(long startTime) { + AbstractMemoryPoolMXBean[] beans = MemoryPoolMXBeansProvider.get().getMXBeans(); + for (int i = 0; i < beans.length; i++) { + /* + * Always add the old generation pool even if we don't end up using it (since we don't + * know what type of collection will happen yet) + */ + setPoolBefore(i, beans[i].getUsedBytes().rawValue(), beans[i].getCommittedBytes().rawValue(), beans[i].getName()); + } + this.startTime = startTime; + } + + @SuppressWarnings("hiding") + public void afterCollection(boolean isIncremental, GCCause cause, long epoch, long endTime) { + AbstractMemoryPoolMXBean[] beans = MemoryPoolMXBeansProvider.get().getMXBeans(); + for (int i = 0; i < beans.length; i++) { + setPoolAfter(i, beans[i].getUsedBytes().rawValue(), beans[i].getCommittedBytes().rawValue(), beans[i].getName()); + } + this.endTime = endTime; + this.isIncremental = isIncremental; + this.cause = cause; + this.epoch = epoch; + this.timestamp = System.currentTimeMillis(); + + } + + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + void setPoolBefore(int index, long used, long committed, String name) { + before[index].set(used, committed, name); + } + + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + void setPoolAfter(int index, long used, long committed, String name) { + after[index].set(used, committed, name); + } + + @Uninterruptible(reason = "Avoid pausing for a GC which could cause races.") + public void copyTo(GcNotificationRequest destination) { + destination.startTime = startTime; + destination.epoch = epoch; + destination.endTime = endTime; + destination.cause = cause; + destination.timestamp = timestamp; + destination.isIncremental = isIncremental; + + for (int index = 0; index < before.length; index++) { + destination.setPoolBefore(index, before[index].getUsed(), before[index].getCommitted(), before[index].getName()); + } + + for (int index = 0; index < after.length; index++) { + destination.setPoolAfter(index, after[index].getUsed(), after[index].getCommitted(), after[index].getName()); + } + } + + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public PoolMemoryUsage getPoolBefore(int i) { + return before[i]; + } + + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public PoolMemoryUsage getPoolAfter(int i) { + return after[i]; + } + + public boolean isIncremental() { + return isIncremental; + } + + public GCCause getCause() { + return cause; + } + + public long getEndTime() { + return endTime; + } + + public long getStartTime() { + return startTime; + } + + public long getTimestamp() { + return timestamp; + } + + public long getEpoch() { + return epoch; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/GcNotifier.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/GcNotifier.java new file mode 100644 index 000000000000..25cad7521e54 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/GcNotifier.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.notification; + +import com.oracle.svm.core.collections.CircularQueue; +import com.oracle.svm.core.heap.GCCause; +import com.oracle.svm.core.gc.AbstractGarbageCollectorMXBean; +import com.oracle.svm.core.thread.VMOperation; +import com.oracle.svm.core.util.BasedOnJDKFile; +import com.oracle.svm.core.Uninterruptible; + +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; + +import jdk.graal.compiler.api.replacements.Fold; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.Platform; + +/** + * This class keeps a circular queue of requests to later use when emitting GC notifications. The + * requests are pre-allocated so that they can be used during a GC. + * + * This class is responsible for enqueuing requests during GC safepoints and later dequeuing + * requests to generate notifications when the notification thread handles signals outside of + * safepoints. + */ +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+19/src/hotspot/share/services/gcNotifier.cpp") // +public class GcNotifier { + // 5 is probably more than enough, although making the queue larger isn't a problem either. + private static final int QUEUE_SIZE = 3; + private final CircularQueue requestQueue; + // This is the request we are emitting a notification for + GcNotificationRequest currentRequest; + /** + * This is cached to handle {@link com.sun.management.GarbageCollectorMXBean#getLastGcInfo()} . + */ + GcNotificationRequest latestRequestComplete; + /** + * This is cached to handle {@link com.sun.management.GarbageCollectorMXBean#getLastGcInfo()} . + */ + GcNotificationRequest latestRequestIncremental; + + @Platforms(Platform.HOSTED_ONLY.class) + GcNotifier() { + // Pre-allocate the queue so we can use it later from allocation free code. + requestQueue = new CircularQueue<>(QUEUE_SIZE, GcNotificationRequest::new); + currentRequest = new GcNotificationRequest(); + latestRequestComplete = new GcNotificationRequest(); + latestRequestIncremental = new GcNotificationRequest(); + } + + @Fold + public static GcNotifier singleton() { + return ImageSingletons.lookup(GcNotifier.class); + } + + /** Called during GC. */ + public void beforeCollection(long startTime) { + assert VMOperation.isInProgressAtSafepoint(); + requestQueue.peekTail().beforeCollection(startTime); + } + + /** Called during GC. */ + public void afterCollection(boolean isIncremental, GCCause cause, long epoch, long endTime) { + assert VMOperation.isInProgressAtSafepoint(); + requestQueue.peekTail().afterCollection(isIncremental, cause, epoch, endTime); + + if (isIncremental) { + requestQueue.peekTail().copyTo(latestRequestIncremental); + } else { + requestQueue.peekTail().copyTo(latestRequestComplete); + } + + requestQueue.advance(); + ImageSingletons.lookup(NotificationThreadSupport.class).signalNotificationThread(); + } + + @Uninterruptible(reason = "Avoid pausing for a GC which could cause races.") + public boolean hasRequest() { + return !requestQueue.isEmpty(); + } + + /** + * Called by the notification thread. Sends a notification if any are queued. If none are + * queued, then return immediately. + */ + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+19/src/hotspot/share/services/gcNotifier.cpp#L191-L225") // + void sendNotification() { + assert !VMOperation.isInProgressAtSafepoint(); + + if (!updateCurrentRequest()) { + // No new requests. + return; + } + + GarbageCollectorMXBean gcBean = getGarbageCollectorMXBean(currentRequest.isIncremental()); + + ((AbstractGarbageCollectorMXBean) gcBean).createNotification(currentRequest); + } + + private static GarbageCollectorMXBean getGarbageCollectorMXBean(boolean isIncremental) { + GarbageCollectorMXBean gcBean = null; + for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) { + if (((AbstractGarbageCollectorMXBean) bean).isIncremental() == isIncremental) { + gcBean = bean; + break; + } + } + assert (gcBean != null); + return gcBean; + } + + /** Called by the notification thread. */ + @Uninterruptible(reason = "Avoid pausing for a GC safepoint which could cause races with pushes to the request queue.") + private boolean updateCurrentRequest() { + // If there's nothing to read, return immediately. + if (!hasRequest()) { + return false; + } + // Move the data to avoid data being overwritten by new writes to the queue. + currentRequest = requestQueue.replaceHead(currentRequest); + requestQueue.advanceHead(); + return true; + } + + /** Called by notification thread. */ + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public GcNotificationRequest getLatestRequest(boolean isIncremental) { + if (isIncremental) { + return latestRequestIncremental; + } + return latestRequestComplete; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/HasGcNotificationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/HasGcNotificationSupport.java new file mode 100644 index 000000000000..2857b5160770 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/HasGcNotificationSupport.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.notification; + +import com.oracle.svm.core.VMInspectionOptions; + +import java.util.function.BooleanSupplier; + +import jdk.graal.compiler.api.replacements.Fold; + +public class HasGcNotificationSupport implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return get(); + } + + @Fold + public static boolean get() { + return VMInspectionOptions.hasGcNotificationSupport(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/NotificationThread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/NotificationThread.java new file mode 100644 index 000000000000..01874c4b18e8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/NotificationThread.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.notification; + +import com.oracle.svm.core.locks.VMCondition; +import com.oracle.svm.core.locks.VMMutex; +import com.oracle.svm.core.log.Log; +import com.oracle.svm.core.thread.PlatformThreads; +import com.oracle.svm.core.util.BasedOnJDKFile; +import com.oracle.svm.core.util.TimeUtils; +import com.oracle.svm.core.Uninterruptible; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +/** + * This class is the dedicated thread that handles services. For now, the only notification is GC + * notifications. + */ +public class NotificationThread extends Thread { + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+19/src/hotspot/share/runtime/notificationThread.cpp#L41") // + private static final String THREAD_NAME = "Notification Thread"; + private final VMMutex mutex; + private final VMCondition condition; + private volatile boolean stopped; + + @Platforms(Platform.HOSTED_ONLY.class) + @SuppressWarnings("this-escape") + public NotificationThread() { + // Hotspot sets the notification thread to the system thread group. + super(PlatformThreads.singleton().systemGroup, THREAD_NAME); + this.mutex = new VMMutex("serviceThread"); + this.condition = new VMCondition(mutex); + setDaemon(true); + } + + /** Awakens to send notifications asynchronously. */ + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+19/src/hotspot/share/runtime/notificationThread.cpp#L52-L91") // + @Override + public void run() { + while (!stopped) { + if (!hasRequests()) { + await(); + } + if (HasGcNotificationSupport.get()) { + GcNotifier.singleton().sendNotification(); + } + // In the future, we may want to do other things here too. + } + } + + private void await() { + mutex.lock(); + try { + while (!hasRequests() && !stopped) { + /* + * It's possible for a signal to get lost here if a GC occurs after we check + * hasRequests() but before we block. So wait a maximum of 3s as a precaution. + */ + condition.block(TimeUtils.millisToNanos(3000)); + } + } finally { + mutex.unlock(); + } + } + + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static boolean hasRequests() { + // In the future, we may check for other pending requests here too. + return GcNotifier.singleton().hasRequest(); + } + + public void signal() { + condition.signal(); + } + + public void shutdown() { + this.stopped = true; + this.signal(); + // Wait until the NotificationThread finishes. + try { + this.join(); + } catch (InterruptedException e) { + Log.log().string("Service thread could not shutdown correctly."); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/NotificationThreadFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/NotificationThreadFeature.java new file mode 100644 index 000000000000..5463d9ae8182 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/NotificationThreadFeature.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.notification; + +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.jdk.RuntimeSupport; +import org.graalvm.nativeimage.ImageSingletons; + +@AutomaticallyRegisteredFeature +public class NotificationThreadFeature implements InternalFeature { + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return HasGcNotificationSupport.get(); // Can extend this list later. + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + ImageSingletons.add(NotificationThreadSupport.class, new NotificationThreadSupport()); + RuntimeSupport.getRuntimeSupport().addStartupHook(new NotificationThreadStartupHook()); + RuntimeSupport.getRuntimeSupport().addShutdownHook(new NotificationThreadShutdownHook()); + } +} + +final class NotificationThreadStartupHook implements RuntimeSupport.Hook { + @Override + public void execute(boolean isFirstIsolate) { + NotificationThreadSupport.singleton().initialize(); + } +} + +final class NotificationThreadShutdownHook implements RuntimeSupport.Hook { + @Override + public void execute(boolean isFirstIsolate) { + NotificationThreadSupport.singleton().teardown(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/NotificationThreadSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/NotificationThreadSupport.java new file mode 100644 index 000000000000..4c64f33eb0c3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/NotificationThreadSupport.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.notification; + +import jdk.graal.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +public class NotificationThreadSupport { + private final NotificationThread notificationThread; + + @Platforms(Platform.HOSTED_ONLY.class) + public NotificationThreadSupport() { + notificationThread = new NotificationThread(); + } + + @Fold + public static NotificationThreadSupport singleton() { + return ImageSingletons.lookup(NotificationThreadSupport.class); + } + + void initialize() { + notificationThread.start(); + } + + public void signalNotificationThread() { + notificationThread.signal(); + } + + void teardown() { + notificationThread.shutdown(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/PoolMemoryUsage.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/PoolMemoryUsage.java new file mode 100644 index 000000000000..35011a951e5b --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/PoolMemoryUsage.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.notification; + +import com.oracle.svm.core.Uninterruptible; + +/** Used from allocation free code and later converted to MemoryUsage. */ +public class PoolMemoryUsage { + private long used; + private long committed; + private String name; + + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void set(long used, long committed, String name) { + this.used = used; + this.committed = committed; + this.name = name; + } + + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getUsed() { + return used; + } + + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getCommitted() { + return committed; + } + + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public String getName() { + return name; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/Target_com_sun_management_GcInfo.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/Target_com_sun_management_GcInfo.java new file mode 100644 index 000000000000..14ceea0fed2e --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/Target_com_sun_management_GcInfo.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.notification; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.TargetClass; +import com.sun.management.GcInfo; + +import java.lang.management.MemoryUsage; + +@TargetClass(value = GcInfo.class, onlyWith = HasGcNotificationSupport.class) +@SuppressWarnings("static-method") +public final class Target_com_sun_management_GcInfo { + @SuppressWarnings("unused") // + @Alias + public Target_com_sun_management_GcInfo(Target_com_sun_management_internal_GcInfoBuilder builder, + long index, long startTime, long endTime, + MemoryUsage[] muBeforeGc, + MemoryUsage[] muAfterGc, + Object[] extAttributes) { + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/Target_com_sun_management_internal_GcInfoBuilder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/Target_com_sun_management_internal_GcInfoBuilder.java new file mode 100644 index 000000000000..c0016054b9bf --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/notification/Target_com_sun_management_internal_GcInfoBuilder.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.notification; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.util.BasedOnJDKFile; +import com.sun.management.internal.GcInfoBuilder; + +import java.lang.management.GarbageCollectorMXBean; + +@TargetClass(value = GcInfoBuilder.class, onlyWith = HasGcNotificationSupport.class) +@SuppressWarnings("static-method") +public final class Target_com_sun_management_internal_GcInfoBuilder { + @SuppressWarnings("unused") // + @Alias + public Target_com_sun_management_internal_GcInfoBuilder(GarbageCollectorMXBean gc, String[] poolNames) { + } + + /** All GCs have 1 attribute (number of GC threads). */ + @Substitute + private int getNumGcExtAttributes(@SuppressWarnings("unused") GarbageCollectorMXBean gc) { + return 1; + } + + /** This is hardcoded in Hotspot. All GCs have 1 attribute (number of GC threads). */ + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+14/src/hotspot/share/services/management.cpp#L1803-L1817") // + @Substitute + private void fillGcAttributeInfo(@SuppressWarnings("unused") GarbageCollectorMXBean gc, + int numAttributes, + String[] attributeNames, + char[] types, + String[] descriptions) { + assert numAttributes == 1; + attributeNames[0] = "GcThreadCount"; + types[0] = 'I'; + descriptions[0] = "Number of GC threads"; + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/native-image.properties b/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/native-image.properties index e1470477f070..a76ecb0e5a94 100644 --- a/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/native-image.properties +++ b/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/native-image.properties @@ -8,5 +8,5 @@ Args= \ --features=com.oracle.svm.test.jfr.JfrTestFeature \ --add-opens=java.base/java.lang=ALL-UNNAMED \ --add-exports=org.graalvm.nativeimage.base/com.oracle.svm.util=ALL-UNNAMED \ - --enable-monitoring=jvmstat,jfr,jmxserver,jmxclient,nmt \ + --enable-monitoring=jvmstat,jfr,jmxserver,jmxclient,nmt,gcnotif \ -J--enable-preview \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/service/GcNotificationTests.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/service/GcNotificationTests.java new file mode 100644 index 000000000000..c5d7c17fc767 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/service/GcNotificationTests.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.test.service; + +import com.oracle.svm.core.gc.AbstractGarbageCollectorMXBean; +import com.sun.management.GarbageCollectionNotificationInfo; +import com.sun.management.GcInfo; + +import org.junit.Test; + +import javax.management.Notification; +import javax.management.NotificationEmitter; +import javax.management.openmbean.CompositeData; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.util.Set; + +import static org.junit.Assert.assertTrue; + +public class GcNotificationTests { + + @Test + public void testGetLastGcInfo() { + System.gc(); + for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) { + if (!(gcBean instanceof NotificationEmitter)) { + continue; + } + GcInfo lastGcInfo = ((com.sun.management.GarbageCollectorMXBean) gcBean).getLastGcInfo(); + if (lastGcInfo != null) { + assertTrue(lastGcInfo.getStartTime() > 0); + assertTrue(lastGcInfo.getId() >= 0); + assertTrue(lastGcInfo.getDuration() >= 0); // Precision is 1 ms. + + Set poolNames = lastGcInfo.getMemoryUsageBeforeGc().keySet(); + for (String poolName : poolNames) { + long before = lastGcInfo.getMemoryUsageBeforeGc().get(poolName).getUsed(); + long after = lastGcInfo.getMemoryUsageAfterGc().get(poolName).getUsed(); + assertTrue(before >= 0); + assertTrue(after >= 0); + } + } + } + } + + @Test + public void testListenerRegistration() { + int count = 0; + // Get the collector beans and register listeners. + for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) { + if (!(gcBean instanceof NotificationEmitter notificationEmitter)) { + continue; + } + count++; + notificationEmitter.addNotificationListener( + (notification, handback) -> { + }, + notification -> notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION), + null); + // Check listeners were registered correctly + assertTrue(((AbstractGarbageCollectorMXBean) gcBean).hasListeners()); + } + assertTrue("There should be some GCs that are notification emitters", count > 0); + } + + @Test + public void testNotificationData() { + NotificationTestListener ntl = new NotificationTestListener(); + + // Register notification listeners + for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) { + if (!(gcBean instanceof NotificationEmitter notificationEmitter)) { + continue; + } + notificationEmitter.addNotificationListener( + ntl, + notification -> notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION), + null); + } + + // Prompt a GC and check for notifications. + synchronized (ntl) { + System.gc(); + try { + // Wait a reasonable maximum length of time. + ntl.wait(30 * 1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + assertTrue(ntl.success); + } + } + + class NotificationTestListener implements javax.management.NotificationListener { + public boolean success; + + @Override + public void handleNotification(Notification notification, Object handback) { + CompositeData cd = (CompositeData) notification.getUserData(); + GarbageCollectionNotificationInfo notificationInfo = GarbageCollectionNotificationInfo.from(cd); + + assertTrue(notificationInfo.getGcName() != null); + + assertTrue(notificationInfo.getGcAction().equals("end of minor GC") || notificationInfo.getGcAction().equals("end of major GC")); + GcInfo gcInfo = notificationInfo.getGcInfo(); + assertTrue(gcInfo != null); + assertTrue(gcInfo.getDuration() >= 0); // Precision is 1 ms. + Set poolNames = gcInfo.getMemoryUsageBeforeGc().keySet(); + for (String poolName : poolNames) { + long before = gcInfo.getMemoryUsageBeforeGc().get(poolName).getUsed(); + long after = gcInfo.getMemoryUsageAfterGc().get(poolName).getUsed(); + assertTrue(before >= 0); + assertTrue(after >= 0); + } + + if (notificationInfo.getGcCause().equals("java.lang.System.gc()")) { + signalFinished(); + } + } + + private synchronized void signalFinished() { + success = true; + notify(); + } + } +}