From 06c6404e12191a588e06eec5a10afbfec2bd4db3 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 26 Feb 2024 16:36:32 -0500 Subject: [PATCH 1/2] Add NMT events to JFR and add peak tracking and some tests. add peak tracking and tests --- .../src/com/oracle/svm/core/jfr/JfrEvent.java | 2 + .../com/oracle/svm/core/jfr/JfrFeature.java | 4 +- .../core/jfr/JfrNmtCategorySerializer.java | 49 ++++++++++ .../src/com/oracle/svm/core/jfr/JfrType.java | 3 +- .../EveryChunkNativePeriodicEvents.java | 67 ++++++++++++- .../events/NativeMemoryUsagePeakEvent.java | 44 +++++++++ .../NativeMemoryUsageTotalPeakEvent.java | 43 ++++++++ .../svm/core/nmt/NativeMemoryTracking.java | 39 +++++++- .../svm/core/nmt/NmtMallocMemoryInfo.java | 47 +++++++-- .../oracle/svm/test/jfr/TestNmtEvents.java | 97 +++++++++++++++++++ .../svm/test/jfr/utils/JfrFileParser.java | 2 + .../NmtCategoryConstantPoolParser.java | 49 ++++++++++ .../test/nmt/NativeMemoryTrackingTests.java | 18 ++++ 13 files changed, 453 insertions(+), 11 deletions(-) create mode 100755 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNmtCategorySerializer.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsagePeakEvent.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsageTotalPeakEvent.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestNmtEvents.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/NmtCategoryConstantPoolParser.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java index 44e4de3e90e6..49c4355ae3a8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java @@ -71,6 +71,8 @@ public final class JfrEvent { public static final JfrEvent AllocationRequiringGC = create("jdk.AllocationRequiringGC"); public static final JfrEvent OldObjectSample = create("jdk.OldObjectSample"); public static final JfrEvent ObjectAllocationSample = create("jdk.ObjectAllocationSample", JfrEventFlags.SupportsThrottling); + public static final JfrEvent NativeMemoryUsage = create("jdk.NativeMemoryUsage"); + public static final JfrEvent NativeMemoryUsageTotal = create("jdk.NativeMemoryUsageTotal"); private final long id; private final String name; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java index 3a7e70b4729c..b1b7212197d9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java @@ -172,7 +172,9 @@ public void afterRegistration(AfterRegistrationAccess access) { JfrSerializerSupport.get().register(new JfrGCNameSerializer()); JfrSerializerSupport.get().register(new JfrVMOperationNameSerializer()); JfrSerializerSupport.get().register(new JfrGCWhenSerializer()); - + if (VMInspectionOptions.hasNativeMemoryTrackingSupport()) { + JfrSerializerSupport.get().register(new JfrNmtCategorySerializer()); + } ThreadListenerSupport.get().register(SubstrateJVM.getThreadLocal()); RuntimeClassInitializationSupport rci = ImageSingletons.lookup(RuntimeClassInitializationSupport.class); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNmtCategorySerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNmtCategorySerializer.java new file mode 100755 index 000000000000..176cf3b0f178 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNmtCategorySerializer.java @@ -0,0 +1,49 @@ +/* + * 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.jfr; + +import com.oracle.svm.core.nmt.NmtCategory; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +public class JfrNmtCategorySerializer implements JfrSerializer { + @Platforms(Platform.HOSTED_ONLY.class) + public JfrNmtCategorySerializer() { + } + + @Override + public void write(JfrChunkWriter writer) { + writer.writeCompressedLong(JfrType.NMTType.getId()); + + NmtCategory[] nmtCategories = NmtCategory.values(); + writer.writeCompressedLong(nmtCategories.length); + for (int i = 0; i < nmtCategories.length; i++) { + writer.writeCompressedInt(i); + writer.writeString(nmtCategories[i].getName()); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java index 4650fda41b87..b3c8603704e6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java @@ -47,7 +47,8 @@ public enum JfrType { GCWhen("jdk.types.GCWhen"), VMOperation("jdk.types.VMOperationType"), MonitorInflationCause("jdk.types.InflateCause"), - OldObject("jdk.types.OldObject"); + OldObject("jdk.types.OldObject"), + NMTType("jdk.types.NMTType"); private final long id; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java index db0a82d9c853..20507e5224b6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -38,8 +39,11 @@ import com.oracle.svm.core.jfr.JfrNativeEventWriterData; import com.oracle.svm.core.jfr.JfrNativeEventWriterDataAccess; import com.oracle.svm.core.jfr.JfrTicks; +import com.oracle.svm.core.nmt.NmtCategory; +import com.oracle.svm.core.nmt.NativeMemoryTracking; import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.VMThreads; +import com.oracle.svm.core.VMInspectionOptions; import jdk.jfr.Event; import jdk.jfr.Name; @@ -54,6 +58,7 @@ public static void emit() { emitPhysicalMemory(); emitClassLoadingStatistics(); emitPerThreadEvents(); + emitNativeMemoryTrackingEvents(); } @Uninterruptible(reason = "Accesses a JFR buffer.") @@ -102,6 +107,66 @@ private static void emitClassLoadingStatistics() { } } + /** + * Emit events for NativeMemoryUsage and NativeMemoryUsageTotal. We do not guarantee consistent + * measurements across NMT categories and the total. Each individual NMT category uses atomic + * counters which may change while we are in this method. Similar to OpenJDK, it is only a + * best-effort approach. + */ + private static void emitNativeMemoryTrackingEvents() { + if (VMInspectionOptions.hasNativeMemoryTrackingSupport()) { + emitJdkNmtEvents(NmtCategory.values()); + emitNmtPeakEvents(); + } + } + + /** These peak events are specific to SubstrateVM. */ + private static void emitNmtPeakEvents() { + NativeMemoryUsageTotalPeakEvent nmtTotalPeakEvent = new NativeMemoryUsageTotalPeakEvent(); + nmtTotalPeakEvent.countAtPeak = NativeMemoryTracking.singleton().getCountAtTotalPeakUsage(); + nmtTotalPeakEvent.peakUsage = NativeMemoryTracking.singleton().getPeakTotalUsedMemory(); + nmtTotalPeakEvent.commit(); + + for (NmtCategory nmtCategory : NmtCategory.values()) { + NativeMemoryUsagePeakEvent nmtPeakEvent = new NativeMemoryUsagePeakEvent(); + nmtPeakEvent.type = nmtCategory.getName(); + nmtPeakEvent.countAtPeak = NativeMemoryTracking.singleton().getCountAtPeakUsage(nmtCategory); + nmtPeakEvent.peakUsage = NativeMemoryTracking.singleton().getPeakUsedMemory(nmtCategory); + nmtPeakEvent.commit(); + } + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void emitJdkNmtEvents(NmtCategory[] nmtCategories) { + long timestamp = JfrTicks.elapsedTicks(); + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); + + if (JfrEvent.NativeMemoryUsage.shouldEmit()) { + for (NmtCategory nmtCategory : nmtCategories) { + JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.NativeMemoryUsage); + JfrNativeEventWriter.putLong(data, timestamp); + /* Category */ + JfrNativeEventWriter.putLong(data, nmtCategory.ordinal()); + /* Reserved usage */ + JfrNativeEventWriter.putLong(data, NativeMemoryTracking.singleton().getUsedMemory(nmtCategory)); + /* Committed usage */ + JfrNativeEventWriter.putLong(data, NativeMemoryTracking.singleton().getUsedMemory(nmtCategory)); + JfrNativeEventWriter.endSmallEvent(data); + } + } + if (JfrEvent.NativeMemoryUsageTotal.shouldEmit()) { + JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.NativeMemoryUsageTotal); + JfrNativeEventWriter.putLong(data, timestamp); + /* Reserved usage */ + JfrNativeEventWriter.putLong(data, NativeMemoryTracking.singleton().getTotalUsedMemory()); + /* Committed usage */ + JfrNativeEventWriter.putLong(data, NativeMemoryTracking.singleton().getTotalUsedMemory()); + JfrNativeEventWriter.endSmallEvent(data); + } + + } + private static void emitPerThreadEvents() { if (needsVMOperation()) { EmitPeriodicPerThreadEventsOperation vmOp = new EmitPeriodicPerThreadEventsOperation(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsagePeakEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsagePeakEvent.java new file mode 100644 index 000000000000..8864ac29ec52 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsagePeakEvent.java @@ -0,0 +1,44 @@ +/* + * 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.jfr.events; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; + +@Name("svm.NativeMemoryUsagePeak") +@Label("Native Memory Usage Peak") +@Description("Information about native memory peak usage of committed virtual memory and malloc.") +@Category("Native Image") +@StackTrace(false) +public class NativeMemoryUsagePeakEvent extends Event { + @Label("Memory Type") public String type; + @Label("Peak Usage") public long peakUsage; + @Label("Count At Peak") public long countAtPeak; +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsageTotalPeakEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsageTotalPeakEvent.java new file mode 100644 index 000000000000..91a978518de0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsageTotalPeakEvent.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.jfr.events; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; + +@Name("svm.NativeMemoryUsageTotalPeak") +@Label("Native Memory Usage Total Peak") +@Description("Information about native memory peak usage of committed virtual memory and malloc.") +@Category("Native Image") +@StackTrace(false) +public class NativeMemoryUsageTotalPeakEvent extends Event { + @Label("Peak Usage") public long peakUsage; + @Label("Count At Peak") public long countAtPeak; +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java index 616cbe1944d0..4a3efa90a907 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java @@ -144,10 +144,41 @@ private static Pointer getInnerPointer(NmtMallocHeader mallocHeader) { return ((Pointer) mallocHeader).add(sizeOfNmtHeader()); } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public long getUsedMemory(NmtCategory category) { return mallocMemorySnapshot.getInfoByCategory(category).getUsed(); } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getPeakUsedMemory(NmtCategory category) { + return mallocMemorySnapshot.getInfoByCategory(category).getPeakUsed(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getCountAtPeakUsage(NmtCategory category) { + return mallocMemorySnapshot.getInfoByCategory(category).getCountAtPeakUsage(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getTotalCount() { + return mallocMemorySnapshot.getTotalInfo().getCount(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getTotalUsedMemory() { + return mallocMemorySnapshot.getTotalInfo().getUsed(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getPeakTotalUsedMemory() { + return mallocMemorySnapshot.getTotalInfo().getPeakUsed(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getCountAtTotalPeakUsage() { + return mallocMemorySnapshot.getTotalInfo().getCountAtPeakUsage(); + } + public static RuntimeSupport.Hook shutdownHook() { return isFirstIsolate -> NativeMemoryTracking.singleton().printStatistics(); } @@ -156,13 +187,17 @@ public void printStatistics() { if (VMInspectionOptions.PrintNMTStatistics.getValue()) { System.out.println(); System.out.println("Native memory tracking"); - System.out.println(" Total used memory: " + mallocMemorySnapshot.getTotalInfo().getUsed() + " bytes"); - System.out.println(" Total alive allocations: " + mallocMemorySnapshot.getTotalInfo().getCount()); + System.out.println(" Peak total used memory: " + getPeakTotalUsedMemory() + " bytes"); + System.out.println(" Total alive allocations at peak usage: " + getCountAtTotalPeakUsage()); + System.out.println(" Total used memory: " + getTotalUsedMemory() + " bytes"); + System.out.println(" Total alive allocations: " + getTotalCount()); for (int i = 0; i < NmtCategory.values().length; i++) { String name = NmtCategory.values()[i].getName(); NmtMallocMemoryInfo info = mallocMemorySnapshot.getInfoByCategory(i); + System.out.println(" " + name + " peak used memory: " + info.getPeakUsed() + " bytes"); + System.out.println(" " + name + " alive allocations at peak: " + info.getCountAtPeakUsage()); System.out.println(" " + name + " used memory: " + info.getUsed() + " bytes"); System.out.println(" " + name + " alive allocations: " + info.getCount()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemoryInfo.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemoryInfo.java index 7a54c3b491ca..6bf524b07cf0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemoryInfo.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemoryInfo.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -26,6 +26,8 @@ package com.oracle.svm.core.nmt; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; @@ -33,32 +35,65 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicLong; +import jdk.internal.misc.Unsafe; + class NmtMallocMemoryInfo { + private static final Unsafe U = Unsafe.getUnsafe(); + protected static final long PEAK_USAGE_OFFSET = U.objectFieldOffset(NmtMallocMemoryInfo.class, "peakUsage"); private final AtomicLong count = new AtomicLong(0); private final AtomicLong used = new AtomicLong(0); + private volatile long peakUsage; + private volatile long peakCount; @Platforms(Platform.HOSTED_ONLY.class) NmtMallocMemoryInfo() { } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) void track(UnsignedWord allocationSize) { - count.incrementAndGet(); - used.addAndGet(allocationSize.rawValue()); + /* + * Similar to Hotspot, we only make a best effort to record the count at the time of the + * peak. Observing the memory used and count is not together atomic. + */ + updatePeak(used.addAndGet(allocationSize.rawValue()), count.incrementAndGet()); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void updatePeak(long newUsed, long newCount) { + long expectedPeakUsage = peakUsage; + while (expectedPeakUsage < newUsed) { + if (U.compareAndSetLong(this, PEAK_USAGE_OFFSET, expectedPeakUsage, newUsed)) { + peakCount = newCount; + return; + } + expectedPeakUsage = peakUsage; + } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) void untrack(UnsignedWord allocationSize) { long lastCount = count.decrementAndGet(); long lastSize = used.addAndGet(-allocationSize.rawValue()); assert lastSize >= 0 && lastCount >= 0; } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) long getUsed() { return used.get(); } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) long getCount() { return count.get(); } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + long getPeakUsed() { + return peakUsage; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + long getCountAtPeakUsage() { + return peakCount; + } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestNmtEvents.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestNmtEvents.java new file mode 100644 index 000000000000..e2cb5f4fdeac --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestNmtEvents.java @@ -0,0 +1,97 @@ +/* + * 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.jfr; + +import static org.junit.Assert.assertTrue; + +import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.memory.NativeMemory; +import com.oracle.svm.core.nmt.NmtCategory; + +import java.util.List; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import org.junit.Test; + +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; + +public class TestNmtEvents extends JfrRecordingTest { + private static final int ALLOCATION_SIZE = 1024 * 16; + + @Test + public void test() throws Throwable { + String[] events = new String[]{ + JfrEvent.NativeMemoryUsage.getName(), + JfrEvent.NativeMemoryUsageTotal.getName(), + "svm.NativeMemoryUsageTotalPeak", + "svm.NativeMemoryUsagePeak" + }; + + Recording recording = startRecording(events); + + Pointer ptr = NativeMemory.malloc(WordFactory.unsigned(ALLOCATION_SIZE), NmtCategory.Code); + + /* Force a chunk rotation to trigger periodic event emission. */ + recording.dump(createTempJfrFile()); + + NativeMemory.free(ptr); + + stopRecording(recording, TestNmtEvents::validateEvents); + } + + private static void validateEvents(List events) { + boolean foundNativeMemoryUsage = false; + boolean foundNativeMemoryUsageTotal = false; + boolean foundNativeMemoryUsageTotalPeak = false; + boolean foundNativeMemoryUsagePeak = false; + + assertTrue(events.size() >= 4); + for (RecordedEvent e : events) { + if (e.getEventType().getName().equals(JfrEvent.NativeMemoryUsage.getName()) && + e.getString("type").equals(NmtCategory.Code.getName())) { + foundNativeMemoryUsage = true; + + } + if (e.getEventType().getName().equals("svm.NativeMemoryUsageTotalPeak")) { + foundNativeMemoryUsageTotalPeak = true; + } + if (e.getEventType().getName().equals("svm.NativeMemoryUsagePeak") && + e.getString("type").equals(NmtCategory.Code.getName())) { + foundNativeMemoryUsagePeak = true; + } + if (e.getEventType().getName().equals(JfrEvent.NativeMemoryUsageTotal.getName())) { + foundNativeMemoryUsageTotal = true; + } + } + assertTrue(foundNativeMemoryUsage); + assertTrue(foundNativeMemoryUsageTotal); + assertTrue(foundNativeMemoryUsagePeak); + assertTrue(foundNativeMemoryUsageTotalPeak); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java index 4898f751a865..f2b17a743fba 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java @@ -54,6 +54,7 @@ import com.oracle.svm.test.jfr.utils.poolparsers.MethodConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.ModuleConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.MonitorInflationCauseConstantPoolParser; +import com.oracle.svm.test.jfr.utils.poolparsers.NmtCategoryConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.OldObjectConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.PackageConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.StacktraceConstantPoolParser; @@ -92,6 +93,7 @@ public JfrFileParser(Path path) { addParser(JfrType.MonitorInflationCause, new MonitorInflationCauseConstantPoolParser(this)); addParser(JfrType.GCWhen, new GCWhenConstantPoolParser(this)); addParser(JfrType.OldObject, new OldObjectConstantPoolParser(this)); + addParser(JfrType.NMTType, new NmtCategoryConstantPoolParser(this)); } private void addParser(JfrType type, ConstantPoolParser parser) { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/NmtCategoryConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/NmtCategoryConstantPoolParser.java new file mode 100644 index 000000000000..404762e869af --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/NmtCategoryConstantPoolParser.java @@ -0,0 +1,49 @@ +/* + * 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.jfr.utils.poolparsers; + +import com.oracle.svm.test.jfr.utils.JfrFileParser; +import com.oracle.svm.test.jfr.utils.RecordingInput; +import org.junit.Assert; + +import java.io.IOException; + +public class NmtCategoryConstantPoolParser extends AbstractSerializerParser { + public NmtCategoryConstantPoolParser(JfrFileParser parser) { + super(parser); + } + + @Override + public void parse(RecordingInput input) throws IOException { + int count = input.readInt(); + Assert.assertTrue(count > 0); + for (int i = 0; i < count; i++) { + addFoundId(input.readInt()); + Assert.assertFalse("NMT category name is empty!", input.readUTF().isEmpty()); + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java index 7c0a5ce10e37..0ccf84d8417e 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java @@ -83,6 +83,24 @@ public void testRealloc() { assertEquals(0, getUsedMemory()); } + @Test + public void testPeakTracking() { + assertEquals(0, getUsedMemory()); + + Pointer ptr1 = NativeMemory.malloc(WordFactory.unsigned(ALLOCATION_SIZE), NmtCategory.Code); + long initialPeak = NativeMemoryTracking.singleton().getPeakUsedMemory(NmtCategory.Code); + assertTrue(initialPeak > 0); + + Pointer ptr2 = NativeMemory.malloc(WordFactory.unsigned(initialPeak * 2), NmtCategory.Code); + long newPeak = NativeMemoryTracking.singleton().getPeakUsedMemory(NmtCategory.Code); + assertTrue(newPeak >= initialPeak * 2); + + NativeMemory.free(ptr1); + NativeMemory.free(ptr2); + assertTrue(NativeMemoryTracking.singleton().getPeakUsedMemory(NmtCategory.Code) == newPeak); + assertEquals(0, getUsedMemory()); + } + private static long getUsedMemory() { return NativeMemoryTracking.singleton().getUsedMemory(NmtCategory.Code); } From 6ba75fbcef873319c6c83d0a21ea25552c3e993f Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Wed, 27 Mar 2024 15:26:51 +0100 Subject: [PATCH 2/2] Cleanups. --- .../com/oracle/svm/core/jfr/JfrFeature.java | 3 +- .../core/jfr/JfrNmtCategorySerializer.java | 9 +-- .../EveryChunkNativePeriodicEvents.java | 37 ++++++----- .../events/NativeMemoryUsagePeakEvent.java | 12 ++-- .../NativeMemoryUsageTotalPeakEvent.java | 12 ++-- .../svm/core/nmt/NativeMemoryTracking.java | 56 +++++++++++------ .../svm/core/nmt/NmtMallocMemoryInfo.java | 31 ++++------ .../svm/core/nmt/NmtMallocMemorySnapshot.java | 62 ------------------- .../oracle/svm/test/jfr/TestNmtEvents.java | 40 ++++++------ .../svm/test/jfr/utils/JfrFileParser.java | 6 +- .../NmtCategoryConstantPoolParser.java | 9 +-- .../test/nmt/NativeMemoryTrackingTests.java | 49 +++++++++------ 12 files changed, 156 insertions(+), 170 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemorySnapshot.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java index b1b7212197d9..0715c7c7f45c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java @@ -27,7 +27,6 @@ import java.util.Collections; import java.util.List; -import com.oracle.svm.core.sampler.SamplerStatistics; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.hosted.Feature; @@ -43,6 +42,7 @@ import com.oracle.svm.core.sampler.SamplerJfrStackTraceSerializer; import com.oracle.svm.core.sampler.SamplerStackTraceSerializer; import com.oracle.svm.core.sampler.SamplerStackWalkVisitor; +import com.oracle.svm.core.sampler.SamplerStatistics; import com.oracle.svm.core.thread.ThreadListenerSupport; import com.oracle.svm.core.thread.ThreadListenerSupportFeature; import com.oracle.svm.core.util.UserError; @@ -175,6 +175,7 @@ public void afterRegistration(AfterRegistrationAccess access) { if (VMInspectionOptions.hasNativeMemoryTrackingSupport()) { JfrSerializerSupport.get().register(new JfrNmtCategorySerializer()); } + ThreadListenerSupport.get().register(SubstrateJVM.getThreadLocal()); RuntimeClassInitializationSupport rci = ImageSingletons.lookup(RuntimeClassInitializationSupport.class); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNmtCategorySerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNmtCategorySerializer.java index 176cf3b0f178..06b01b3a988d 100755 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNmtCategorySerializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNmtCategorySerializer.java @@ -26,10 +26,11 @@ package com.oracle.svm.core.jfr; -import com.oracle.svm.core.nmt.NmtCategory; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.core.nmt.NmtCategory; + public class JfrNmtCategorySerializer implements JfrSerializer { @Platforms(Platform.HOSTED_ONLY.class) public JfrNmtCategorySerializer() { @@ -41,9 +42,9 @@ public void write(JfrChunkWriter writer) { NmtCategory[] nmtCategories = NmtCategory.values(); writer.writeCompressedLong(nmtCategories.length); - for (int i = 0; i < nmtCategories.length; i++) { - writer.writeCompressedInt(i); - writer.writeString(nmtCategories[i].getName()); + for (NmtCategory nmtCategory : nmtCategories) { + writer.writeCompressedInt(nmtCategory.ordinal()); + writer.writeString(nmtCategory.getName()); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java index 20507e5224b6..07a2f37357a9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java @@ -30,6 +30,7 @@ import org.graalvm.nativeimage.StackValue; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.VMInspectionOptions; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.heap.PhysicalMemory; import com.oracle.svm.core.heap.VMOperationInfos; @@ -39,11 +40,10 @@ import com.oracle.svm.core.jfr.JfrNativeEventWriterData; import com.oracle.svm.core.jfr.JfrNativeEventWriterDataAccess; import com.oracle.svm.core.jfr.JfrTicks; -import com.oracle.svm.core.nmt.NmtCategory; import com.oracle.svm.core.nmt.NativeMemoryTracking; +import com.oracle.svm.core.nmt.NmtCategory; import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.VMThreads; -import com.oracle.svm.core.VMInspectionOptions; import jdk.jfr.Event; import jdk.jfr.Name; @@ -120,18 +120,24 @@ private static void emitNativeMemoryTrackingEvents() { } } - /** These peak events are specific to SubstrateVM. */ + /** Emit Native Image-specific events that report the peak memory usage. */ private static void emitNmtPeakEvents() { NativeMemoryUsageTotalPeakEvent nmtTotalPeakEvent = new NativeMemoryUsageTotalPeakEvent(); + + long totalPeakUsed = NativeMemoryTracking.singleton().getPeakTotalUsedMemory(); + nmtTotalPeakEvent.peakCommitted = totalPeakUsed; + nmtTotalPeakEvent.peakReserved = totalPeakUsed; nmtTotalPeakEvent.countAtPeak = NativeMemoryTracking.singleton().getCountAtTotalPeakUsage(); - nmtTotalPeakEvent.peakUsage = NativeMemoryTracking.singleton().getPeakTotalUsedMemory(); nmtTotalPeakEvent.commit(); for (NmtCategory nmtCategory : NmtCategory.values()) { NativeMemoryUsagePeakEvent nmtPeakEvent = new NativeMemoryUsagePeakEvent(); nmtPeakEvent.type = nmtCategory.getName(); + + long peakUsed = NativeMemoryTracking.singleton().getPeakUsedMemory(nmtCategory); + nmtPeakEvent.peakCommitted = peakUsed; + nmtPeakEvent.peakReserved = peakUsed; nmtPeakEvent.countAtPeak = NativeMemoryTracking.singleton().getCountAtPeakUsage(nmtCategory); - nmtPeakEvent.peakUsage = NativeMemoryTracking.singleton().getPeakUsedMemory(nmtCategory); nmtPeakEvent.commit(); } } @@ -144,27 +150,26 @@ private static void emitJdkNmtEvents(NmtCategory[] nmtCategories) { if (JfrEvent.NativeMemoryUsage.shouldEmit()) { for (NmtCategory nmtCategory : nmtCategories) { + long usedMemory = NativeMemoryTracking.singleton().getUsedMemory(nmtCategory); + JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.NativeMemoryUsage); JfrNativeEventWriter.putLong(data, timestamp); - /* Category */ JfrNativeEventWriter.putLong(data, nmtCategory.ordinal()); - /* Reserved usage */ - JfrNativeEventWriter.putLong(data, NativeMemoryTracking.singleton().getUsedMemory(nmtCategory)); - /* Committed usage */ - JfrNativeEventWriter.putLong(data, NativeMemoryTracking.singleton().getUsedMemory(nmtCategory)); + JfrNativeEventWriter.putLong(data, usedMemory); // reserved + JfrNativeEventWriter.putLong(data, usedMemory); // committed JfrNativeEventWriter.endSmallEvent(data); } } + if (JfrEvent.NativeMemoryUsageTotal.shouldEmit()) { + long totalUsedMemory = NativeMemoryTracking.singleton().getTotalUsedMemory(); + JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.NativeMemoryUsageTotal); JfrNativeEventWriter.putLong(data, timestamp); - /* Reserved usage */ - JfrNativeEventWriter.putLong(data, NativeMemoryTracking.singleton().getTotalUsedMemory()); - /* Committed usage */ - JfrNativeEventWriter.putLong(data, NativeMemoryTracking.singleton().getTotalUsedMemory()); + JfrNativeEventWriter.putLong(data, totalUsedMemory); // reserved + JfrNativeEventWriter.putLong(data, totalUsedMemory); // committed JfrNativeEventWriter.endSmallEvent(data); } - } private static void emitPerThreadEvents() { @@ -176,7 +181,7 @@ private static void emitPerThreadEvents() { @Uninterruptible(reason = "Used to avoid the VM operation if it is not absolutely needed.") private static boolean needsVMOperation() { - /* The returned value is racy. */ + /* The returned value is racy but this is fine because we recheck in the VM operation. */ return JfrEvent.ThreadCPULoad.shouldEmit() || JfrEvent.ThreadAllocationStatistics.shouldEmit(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsagePeakEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsagePeakEvent.java index 8864ac29ec52..a3762683e565 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsagePeakEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsagePeakEvent.java @@ -28,17 +28,21 @@ import jdk.jfr.Category; import jdk.jfr.Description; import jdk.jfr.Event; +import jdk.jfr.Experimental; import jdk.jfr.Label; import jdk.jfr.Name; import jdk.jfr.StackTrace; -@Name("svm.NativeMemoryUsagePeak") +/** Similar to the JFR event jdk.NativeMemoryUsage, except that it tracks the peak usage. */ +@Experimental +@Name("jdk.NativeMemoryUsagePeak") @Label("Native Memory Usage Peak") -@Description("Information about native memory peak usage of committed virtual memory and malloc.") -@Category("Native Image") +@Description("Native memory peak usage for a given memory type in the JVM (GraalVM Native Image only).") +@Category({"Java Virtual Machine", "Memory"}) @StackTrace(false) public class NativeMemoryUsagePeakEvent extends Event { @Label("Memory Type") public String type; - @Label("Peak Usage") public long peakUsage; + @Label("Peak Reserved") public long peakReserved; + @Label("Peak Committed") public long peakCommitted; @Label("Count At Peak") public long countAtPeak; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsageTotalPeakEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsageTotalPeakEvent.java index 91a978518de0..3e615a41c1e3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsageTotalPeakEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsageTotalPeakEvent.java @@ -28,16 +28,20 @@ import jdk.jfr.Category; import jdk.jfr.Description; import jdk.jfr.Event; +import jdk.jfr.Experimental; import jdk.jfr.Label; import jdk.jfr.Name; import jdk.jfr.StackTrace; -@Name("svm.NativeMemoryUsageTotalPeak") +/** Similar to the JFR event jdk.NativeMemoryUsageTotal, except that it tracks the peak usage. */ +@Experimental +@Name("jdk.NativeMemoryUsageTotalPeak") @Label("Native Memory Usage Total Peak") -@Description("Information about native memory peak usage of committed virtual memory and malloc.") -@Category("Native Image") +@Description("Total native memory peak usage for the JVM (GraalVM Native Image only). Might not be the exact sum of the NativeMemoryUsagePeak events due to timing.") +@Category({"Java Virtual Machine", "Memory"}) @StackTrace(false) public class NativeMemoryUsageTotalPeakEvent extends Event { - @Label("Peak Usage") public long peakUsage; + @Label("Peak Reserved") public long peakReserved; + @Label("Peak Committed") public long peakCommitted; @Label("Count At Peak") public long countAtPeak; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java index 4a3efa90a907..149d897ede1a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java @@ -57,10 +57,17 @@ public class NativeMemoryTracking { private static final UnsignedWord ALIGNMENT = WordFactory.unsigned(16); private static final int MAGIC = 0xF0F1F2F3; - private final NmtMallocMemorySnapshot mallocMemorySnapshot = new NmtMallocMemorySnapshot(); + + private final NmtMallocMemoryInfo[] categories; + private final NmtMallocMemoryInfo total; @Platforms(Platform.HOSTED_ONLY.class) public NativeMemoryTracking() { + total = new NmtMallocMemoryInfo(); + categories = new NmtMallocMemoryInfo[NmtCategory.values().length]; + for (int i = 0; i < categories.length; i++) { + categories[i] = new NmtMallocMemoryInfo(); + } } @Fold @@ -71,7 +78,7 @@ public static NativeMemoryTracking singleton() { @Fold public static UnsignedWord sizeOfNmtHeader() { /* - * Align the header to 16 bytes to preserve platform-specific malloc alignments up to 16 + * Align the header to 16 bytes to preserve platform-specific malloc alignment up to 16 * bytes (i.e., the allocation payload is aligned to 16 bytes if the platform-specific * malloc implementation returns a pointer that is aligned to at least 16 bytes). */ @@ -109,9 +116,9 @@ public void track(PointerBase innerPtr) { UnsignedWord allocationSize = header.getAllocationSize(); UnsignedWord totalSize = allocationSize.add(nmtHeaderSize); - mallocMemorySnapshot.getInfoByCategory(header.getCategory()).track(allocationSize); - mallocMemorySnapshot.getInfoByCategory(NmtCategory.NMT).track(nmtHeaderSize); - mallocMemorySnapshot.getTotalInfo().track(totalSize); + getInfo(header.getCategory()).track(allocationSize); + getInfo(NmtCategory.NMT).track(nmtHeaderSize); + total.track(totalSize); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) @@ -127,9 +134,9 @@ public PointerBase untrack(PointerBase innerPtr) { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public void untrack(UnsignedWord size, int category) { - mallocMemorySnapshot.getInfoByCategory(category).untrack(size); - mallocMemorySnapshot.getInfoByCategory(NmtCategory.NMT).untrack(sizeOfNmtHeader()); - mallocMemorySnapshot.getTotalInfo().untrack(size.add(sizeOfNmtHeader())); + getInfo(category).untrack(size); + getInfo(NmtCategory.NMT).untrack(sizeOfNmtHeader()); + total.untrack(size.add(sizeOfNmtHeader())); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) @@ -146,44 +153,55 @@ private static Pointer getInnerPointer(NmtMallocHeader mallocHeader) { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public long getUsedMemory(NmtCategory category) { - return mallocMemorySnapshot.getInfoByCategory(category).getUsed(); + return getInfo(category).getUsed(); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public long getPeakUsedMemory(NmtCategory category) { - return mallocMemorySnapshot.getInfoByCategory(category).getPeakUsed(); + return getInfo(category).getPeakUsed(); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public long getCountAtPeakUsage(NmtCategory category) { - return mallocMemorySnapshot.getInfoByCategory(category).getCountAtPeakUsage(); + return getInfo(category).getCountAtPeakUsage(); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public long getTotalCount() { - return mallocMemorySnapshot.getTotalInfo().getCount(); + return total.getCount(); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public long getTotalUsedMemory() { - return mallocMemorySnapshot.getTotalInfo().getUsed(); + return total.getUsed(); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public long getPeakTotalUsedMemory() { - return mallocMemorySnapshot.getTotalInfo().getPeakUsed(); + return total.getPeakUsed(); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public long getCountAtTotalPeakUsage() { - return mallocMemorySnapshot.getTotalInfo().getCountAtPeakUsage(); + return total.getCountAtPeakUsage(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private NmtMallocMemoryInfo getInfo(NmtCategory category) { + return getInfo(category.ordinal()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private NmtMallocMemoryInfo getInfo(int category) { + assert category < categories.length; + return categories[category]; } public static RuntimeSupport.Hook shutdownHook() { return isFirstIsolate -> NativeMemoryTracking.singleton().printStatistics(); } - public void printStatistics() { + private void printStatistics() { if (VMInspectionOptions.PrintNMTStatistics.getValue()) { System.out.println(); System.out.println("Native memory tracking"); @@ -194,12 +212,12 @@ public void printStatistics() { for (int i = 0; i < NmtCategory.values().length; i++) { String name = NmtCategory.values()[i].getName(); - NmtMallocMemoryInfo info = mallocMemorySnapshot.getInfoByCategory(i); + NmtMallocMemoryInfo info = getInfo(i); System.out.println(" " + name + " peak used memory: " + info.getPeakUsed() + " bytes"); System.out.println(" " + name + " alive allocations at peak: " + info.getCountAtPeakUsage()); - System.out.println(" " + name + " used memory: " + info.getUsed() + " bytes"); - System.out.println(" " + name + " alive allocations: " + info.getCount()); + System.out.println(" " + name + " currently used memory: " + info.getUsed() + " bytes"); + System.out.println(" " + name + " currently alive allocations: " + info.getCount()); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemoryInfo.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemoryInfo.java index 6bf524b07cf0..bdd192413aae 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemoryInfo.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemoryInfo.java @@ -35,15 +35,11 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicLong; -import jdk.internal.misc.Unsafe; - class NmtMallocMemoryInfo { - private static final Unsafe U = Unsafe.getUnsafe(); - protected static final long PEAK_USAGE_OFFSET = U.objectFieldOffset(NmtMallocMemoryInfo.class, "peakUsage"); private final AtomicLong count = new AtomicLong(0); private final AtomicLong used = new AtomicLong(0); - private volatile long peakUsage; - private volatile long peakCount; + private final AtomicLong countAtPeakUsage = new AtomicLong(0); + private final AtomicLong peakUsed = new AtomicLong(0); @Platforms(Platform.HOSTED_ONLY.class) NmtMallocMemoryInfo() { @@ -51,22 +47,21 @@ class NmtMallocMemoryInfo { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) void track(UnsignedWord allocationSize) { - /* - * Similar to Hotspot, we only make a best effort to record the count at the time of the - * peak. Observing the memory used and count is not together atomic. - */ - updatePeak(used.addAndGet(allocationSize.rawValue()), count.incrementAndGet()); + long newUsed = used.addAndGet(allocationSize.rawValue()); + long newCount = count.incrementAndGet(); + updatePeak(newUsed, newCount); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private void updatePeak(long newUsed, long newCount) { - long expectedPeakUsage = peakUsage; - while (expectedPeakUsage < newUsed) { - if (U.compareAndSetLong(this, PEAK_USAGE_OFFSET, expectedPeakUsage, newUsed)) { - peakCount = newCount; + long oldUsed = peakUsed.get(); + while (oldUsed < newUsed) { + if (peakUsed.compareAndSet(oldUsed, newUsed)) { + /* Recording the count at peak usage is racy (similar to Hotspot). */ + countAtPeakUsage.set(newCount); return; } - expectedPeakUsage = peakUsage; + oldUsed = peakUsed.get(); } } @@ -89,11 +84,11 @@ long getCount() { @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) long getPeakUsed() { - return peakUsage; + return peakUsed.get(); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) long getCountAtPeakUsage() { - return peakCount; + return countAtPeakUsage.get(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemorySnapshot.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemorySnapshot.java deleted file mode 100644 index d5a8bca94a31..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemorySnapshot.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, 2023, 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.nmt; - -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; - -import com.oracle.svm.core.Uninterruptible; - -class NmtMallocMemorySnapshot { - private final NmtMallocMemoryInfo[] categories; - private final NmtMallocMemoryInfo total; - - @Platforms(Platform.HOSTED_ONLY.class) - NmtMallocMemorySnapshot() { - total = new NmtMallocMemoryInfo(); - categories = new NmtMallocMemoryInfo[NmtCategory.values().length]; - for (int i = 0; i < categories.length; i++) { - categories[i] = new NmtMallocMemoryInfo(); - } - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - NmtMallocMemoryInfo getInfoByCategory(NmtCategory category) { - return getInfoByCategory(category.ordinal()); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - NmtMallocMemoryInfo getInfoByCategory(int category) { - assert category < categories.length; - return categories[category]; - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - NmtMallocMemoryInfo getTotalInfo() { - return total; - } -} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestNmtEvents.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestNmtEvents.java index e2cb5f4fdeac..45c40797b492 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestNmtEvents.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestNmtEvents.java @@ -28,18 +28,18 @@ import static org.junit.Assert.assertTrue; +import java.util.List; + +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; +import org.junit.Test; + import com.oracle.svm.core.jfr.JfrEvent; import com.oracle.svm.core.memory.NativeMemory; import com.oracle.svm.core.nmt.NmtCategory; -import java.util.List; - import jdk.jfr.Recording; import jdk.jfr.consumer.RecordedEvent; -import org.junit.Test; - -import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; public class TestNmtEvents extends JfrRecordingTest { private static final int ALLOCATION_SIZE = 1024 * 16; @@ -49,10 +49,9 @@ public void test() throws Throwable { String[] events = new String[]{ JfrEvent.NativeMemoryUsage.getName(), JfrEvent.NativeMemoryUsageTotal.getName(), - "svm.NativeMemoryUsageTotalPeak", - "svm.NativeMemoryUsagePeak" + "jdk.NativeMemoryUsagePeak", + "jdk.NativeMemoryUsageTotalPeak" }; - Recording recording = startRecording(events); Pointer ptr = NativeMemory.malloc(WordFactory.unsigned(ALLOCATION_SIZE), NmtCategory.Code); @@ -66,32 +65,35 @@ public void test() throws Throwable { } private static void validateEvents(List events) { + assertTrue(events.size() >= 4); + boolean foundNativeMemoryUsage = false; + boolean foundNativeMemoryUsagePeak = false; boolean foundNativeMemoryUsageTotal = false; boolean foundNativeMemoryUsageTotalPeak = false; - boolean foundNativeMemoryUsagePeak = false; - assertTrue(events.size() >= 4); for (RecordedEvent e : events) { - if (e.getEventType().getName().equals(JfrEvent.NativeMemoryUsage.getName()) && - e.getString("type").equals(NmtCategory.Code.getName())) { + String eventName = e.getEventType().getName(); + if (eventName.equals(JfrEvent.NativeMemoryUsage.getName()) && e.getString("type").equals(NmtCategory.Code.getName())) { foundNativeMemoryUsage = true; - } - if (e.getEventType().getName().equals("svm.NativeMemoryUsageTotalPeak")) { + + if (eventName.equals("jdk.NativeMemoryUsageTotalPeak")) { foundNativeMemoryUsageTotalPeak = true; } - if (e.getEventType().getName().equals("svm.NativeMemoryUsagePeak") && - e.getString("type").equals(NmtCategory.Code.getName())) { + + if (eventName.equals("jdk.NativeMemoryUsagePeak") && e.getString("type").equals(NmtCategory.Code.getName())) { foundNativeMemoryUsagePeak = true; } - if (e.getEventType().getName().equals(JfrEvent.NativeMemoryUsageTotal.getName())) { + + if (eventName.equals(JfrEvent.NativeMemoryUsageTotal.getName())) { foundNativeMemoryUsageTotal = true; } } + assertTrue(foundNativeMemoryUsage); - assertTrue(foundNativeMemoryUsageTotal); assertTrue(foundNativeMemoryUsagePeak); + assertTrue(foundNativeMemoryUsageTotal); assertTrue(foundNativeMemoryUsageTotalPeak); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java index f2b17a743fba..461ff4020a29 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java @@ -38,6 +38,7 @@ import java.util.Collection; import java.util.HashMap; +import com.oracle.svm.core.VMInspectionOptions; import com.oracle.svm.core.jfr.JfrCheckpointType; import com.oracle.svm.core.jfr.JfrChunkFileWriter; import com.oracle.svm.core.jfr.JfrReservedEvent; @@ -93,7 +94,10 @@ public JfrFileParser(Path path) { addParser(JfrType.MonitorInflationCause, new MonitorInflationCauseConstantPoolParser(this)); addParser(JfrType.GCWhen, new GCWhenConstantPoolParser(this)); addParser(JfrType.OldObject, new OldObjectConstantPoolParser(this)); - addParser(JfrType.NMTType, new NmtCategoryConstantPoolParser(this)); + + if (VMInspectionOptions.hasNativeMemoryTrackingSupport()) { + addParser(JfrType.NMTType, new NmtCategoryConstantPoolParser(this)); + } } private void addParser(JfrType type, ConstantPoolParser parser) { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/NmtCategoryConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/NmtCategoryConstantPoolParser.java index 404762e869af..52db8ee8b6c7 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/NmtCategoryConstantPoolParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/NmtCategoryConstantPoolParser.java @@ -26,11 +26,12 @@ package com.oracle.svm.test.jfr.utils.poolparsers; -import com.oracle.svm.test.jfr.utils.JfrFileParser; -import com.oracle.svm.test.jfr.utils.RecordingInput; +import java.io.IOException; + import org.junit.Assert; -import java.io.IOException; +import com.oracle.svm.test.jfr.utils.JfrFileParser; +import com.oracle.svm.test.jfr.utils.RecordingInput; public class NmtCategoryConstantPoolParser extends AbstractSerializerParser { public NmtCategoryConstantPoolParser(JfrFileParser parser) { @@ -43,7 +44,7 @@ public void parse(RecordingInput input) throws IOException { Assert.assertTrue(count > 0); for (int i = 0; i < count; i++) { addFoundId(input.readInt()); - Assert.assertFalse("NMT category name is empty!", input.readUTF().isEmpty()); + Assert.assertFalse("NMT category name is empty!", input.readUTF().isBlank()); } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java index 0ccf84d8417e..6f633651ae44 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java @@ -38,15 +38,15 @@ import com.oracle.svm.core.nmt.NmtCategory; public class NativeMemoryTrackingTests { - private static final int ALLOCATION_SIZE = 1024 * 16; - private static final int REALLOC_SIZE = ALLOCATION_SIZE / 2; + private static final int K = 1024; + private static final int M = 1024 * 1024; @Test public void testMalloc() { assertEquals(0, getUsedMemory()); - Pointer ptr = NativeMemory.malloc(WordFactory.unsigned(ALLOCATION_SIZE), NmtCategory.Code); - assertEquals(ALLOCATION_SIZE, getUsedMemory()); + Pointer ptr = NativeMemory.malloc(16 * K, NmtCategory.Code); + assertEquals(16 * K, getUsedMemory()); assertTrue(getUsedMemory() > 0); NativeMemory.free(ptr); @@ -57,9 +57,9 @@ public void testMalloc() { @Test public void testCalloc() { assertEquals(0, getUsedMemory()); - Pointer ptr = NativeMemory.calloc(WordFactory.unsigned(ALLOCATION_SIZE), NmtCategory.Code); + Pointer ptr = NativeMemory.calloc(16 * K, NmtCategory.Code); - assertEquals(ALLOCATION_SIZE, getUsedMemory()); + assertEquals(16 * K, getUsedMemory()); assertTrue(getUsedMemory() > 0); NativeMemory.free(ptr); @@ -70,14 +70,13 @@ public void testCalloc() { @Test public void testRealloc() { assertEquals(0, getUsedMemory()); - Pointer ptr = NativeMemory.malloc(WordFactory.unsigned(ALLOCATION_SIZE), NmtCategory.Code); + Pointer ptr = NativeMemory.malloc(16 * K, NmtCategory.Code); - assertEquals(getUsedMemory(), ALLOCATION_SIZE); + assertEquals(getUsedMemory(), 16 * K); assertTrue(getUsedMemory() > 0); - Pointer reallocPtr = NativeMemory.realloc(ptr, WordFactory.unsigned(REALLOC_SIZE), NmtCategory.Code); - - assertEquals(REALLOC_SIZE, getUsedMemory()); + Pointer reallocPtr = NativeMemory.realloc(ptr, WordFactory.unsigned(8 * K), NmtCategory.Code); + assertEquals(8 * K, getUsedMemory()); NativeMemory.free(reallocPtr); assertEquals(0, getUsedMemory()); @@ -87,18 +86,32 @@ public void testRealloc() { public void testPeakTracking() { assertEquals(0, getUsedMemory()); - Pointer ptr1 = NativeMemory.malloc(WordFactory.unsigned(ALLOCATION_SIZE), NmtCategory.Code); - long initialPeak = NativeMemoryTracking.singleton().getPeakUsedMemory(NmtCategory.Code); - assertTrue(initialPeak > 0); + Pointer ptr1 = NativeMemory.malloc(M, NmtCategory.Code); + long peakUsed = NativeMemoryTracking.singleton().getPeakUsedMemory(NmtCategory.Code); + assertEquals(M, peakUsed); - Pointer ptr2 = NativeMemory.malloc(WordFactory.unsigned(initialPeak * 2), NmtCategory.Code); - long newPeak = NativeMemoryTracking.singleton().getPeakUsedMemory(NmtCategory.Code); - assertTrue(newPeak >= initialPeak * 2); + Pointer ptr2 = NativeMemory.malloc(M, NmtCategory.Code); + peakUsed = NativeMemoryTracking.singleton().getPeakUsedMemory(NmtCategory.Code); + assertEquals(2 * M, peakUsed); NativeMemory.free(ptr1); + ptr1 = WordFactory.nullPointer(); + NativeMemory.free(ptr2); - assertTrue(NativeMemoryTracking.singleton().getPeakUsedMemory(NmtCategory.Code) == newPeak); + ptr2 = WordFactory.nullPointer(); + + assertEquals(0, getUsedMemory()); + assertEquals(2 * M, NativeMemoryTracking.singleton().getPeakUsedMemory(NmtCategory.Code)); + + Pointer ptr3 = NativeMemory.malloc(3 * M, NmtCategory.Code); + peakUsed = NativeMemoryTracking.singleton().getPeakUsedMemory(NmtCategory.Code); + assertEquals(3 * M, peakUsed); + + NativeMemory.free(ptr3); + ptr3 = WordFactory.nullPointer(); + assertEquals(0, getUsedMemory()); + assertEquals(3 * M, NativeMemoryTracking.singleton().getPeakUsedMemory(NmtCategory.Code)); } private static long getUsedMemory() {