diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java index 3bfd58aa59e9..e60101e3eb80 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java @@ -73,6 +73,8 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; import com.oracle.svm.core.threadlocal.FastThreadLocalWord; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.jfr.events.ObjectAllocationInNewTLABEvent; +import com.oracle.svm.core.jfr.JfrTicks; /** * Bump-pointer allocation from thread-local top and end Pointers. Many of these methods are called @@ -226,6 +228,7 @@ private static Object slowPathNewInstance(Word objectHeader) { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate in the implementation of allocation.") private static Object slowPathNewInstanceWithoutAllocating(DynamicHub hub) { DeoptTester.disableDeoptTesting(); + long startTicks = JfrTicks.elapsedTicks(); try { HeapImpl.exitIfAllocationDisallowed("ThreadLocalAllocation.slowPathNewInstanceWithoutAllocating", DynamicHub.toClass(hub).getName()); GCImpl.getGCImpl().maybeCollectOnAllocation(); @@ -233,6 +236,10 @@ private static Object slowPathNewInstanceWithoutAllocating(DynamicHub hub) { AlignedHeader newTlab = HeapImpl.getChunkProvider().produceAlignedChunk(); return allocateInstanceInNewTlab(hub, newTlab); } finally { + ObjectAllocationInNewTLABEvent.emit(startTicks, + DynamicHub.toClass(hub), + LayoutEncoding.getPureInstanceSize(hub.getLayoutEncoding()).rawValue(), + HeapParameters.getAlignedHeapChunkSize().rawValue()); DeoptTester.enableDeoptTesting(); } } @@ -287,6 +294,8 @@ private static Object slowPathNewArrayLikeObject(Word objectHeader, int length, @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate in the implementation of allocation.") private static Object slowPathNewArrayLikeObject0(DynamicHub hub, int length, UnsignedWord size, byte[] podReferenceMap) { DeoptTester.disableDeoptTesting(); + long startTicks = JfrTicks.elapsedTicks(); + long tlabSize = HeapParameters.getAlignedHeapChunkSize().rawValue(); try { HeapImpl.exitIfAllocationDisallowed("ThreadLocalAllocation.slowPathNewArrayOrPodWithoutAllocating", DynamicHub.toClass(hub).getName()); GCImpl.getGCImpl().maybeCollectOnAllocation(); @@ -295,6 +304,7 @@ private static Object slowPathNewArrayLikeObject0(DynamicHub hub, int length, Un /* Large arrays go into their own unaligned chunk. */ boolean needsZeroing = !HeapChunkProvider.areUnalignedChunksZeroed(); UnalignedHeapChunk.UnalignedHeader newTlabChunk = HeapImpl.getChunkProvider().produceUnalignedChunk(size); + tlabSize = UnalignedHeapChunk.getChunkSizeForObject(size).rawValue(); return allocateLargeArrayLikeObjectInNewTlab(hub, length, size, newTlabChunk, needsZeroing, podReferenceMap); } /* Small arrays go into the regular aligned chunk. */ @@ -308,6 +318,7 @@ private static Object slowPathNewArrayLikeObject0(DynamicHub hub, int length, Un } return array; } finally { + ObjectAllocationInNewTLABEvent.emit(startTicks, DynamicHub.toClass(hub), size.rawValue(), tlabSize); DeoptTester.enableDeoptTesting(); } } 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 bff2c58fb9a5..8137d18ffb8c 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 @@ -58,6 +58,7 @@ public final class JfrEvent { public static final JfrEvent JavaMonitorEnter = create("jdk.JavaMonitorEnter"); public static final JfrEvent ThreadPark = create("jdk.ThreadPark"); public static final JfrEvent JavaMonitorWait = create("jdk.JavaMonitorWait"); + public static final JfrEvent ObjectAllocationInNewTLAB = create("jdk.ObjectAllocationInNewTLAB"); private final long id; private final String name; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationInNewTLABEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationInNewTLABEvent.java new file mode 100644 index 000000000000..317bbf4d4819 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationInNewTLABEvent.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2022, 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 com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.jfr.JfrNativeEventWriter; +import com.oracle.svm.core.jfr.JfrNativeEventWriterData; +import com.oracle.svm.core.jfr.JfrNativeEventWriterDataAccess; +import com.oracle.svm.core.jfr.SubstrateJVM; +import com.oracle.svm.core.jfr.HasJfrSupport; +import org.graalvm.nativeimage.StackValue; + +public class ObjectAllocationInNewTLABEvent { + public static void emit(long startTicks, Class clazz, long allocationSize, long tlabSize) { + if (HasJfrSupport.get()) { + emit0(startTicks, clazz, allocationSize, tlabSize); + } + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void emit0(long startTicks, Class clazz, long allocationSize, long tlabSize) { + if (JfrEvent.ObjectAllocationInNewTLAB.shouldEmit()) { + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); + JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ObjectAllocationInNewTLAB); + JfrNativeEventWriter.putLong(data, startTicks); + JfrNativeEventWriter.putEventThread(data); + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationInNewTLAB, 0)); + JfrNativeEventWriter.putClass(data, clazz); + JfrNativeEventWriter.putLong(data, allocationSize); + JfrNativeEventWriter.putLong(data, tlabSize); + JfrNativeEventWriter.endSmallEvent(data); + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestObjectAllocationInNewTLABEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestObjectAllocationInNewTLABEvent.java new file mode 100644 index 000000000000..d9209ed767c6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestObjectAllocationInNewTLABEvent.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2022, 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 org.junit.Test; + +import com.oracle.svm.core.jfr.JfrEvent; + +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedThread; + +import java.util.Arrays; + +public class TestObjectAllocationInNewTLABEvent extends JfrTest { + static final int KILO = 1024; + // the default size for serial and epsilon GC in SVM. + static final int DEFAULT_ALIGNED_HEAP_CHUNK_SIZE = KILO * KILO; + + public static Helper helper = null; + public static byte[] byteArray = null; + + @Override + public String[] getTestedEvents() { + return new String[]{JfrEvent.ObjectAllocationInNewTLAB.getName()}; + } + + @Override + public void validateEvents() throws Throwable { + boolean foundBigByte = false; + boolean foundSmallByte = false; + boolean foundBigChar = false; + boolean foundInstance = false; + for (RecordedEvent event : getEvents()) { + String eventThread = event. getValue("eventThread").getJavaName(); + if (!eventThread.equals("main")) { + continue; + } + // >= To account for size of reference + if (event. getValue("allocationSize").longValue() >= (2 * DEFAULT_ALIGNED_HEAP_CHUNK_SIZE) && + event. getValue("tlabSize").longValue() >= (2 * DEFAULT_ALIGNED_HEAP_CHUNK_SIZE)) { + // verify previous owner + if (event. getValue("objectClass").getName().equals(char[].class.getName())) { + foundBigChar = true; + } else if (event. getValue("objectClass").getName().equals(byte[].class.getName())) { + foundBigByte = true; + } + } else if (event. getValue("allocationSize").longValue() >= KILO && event. getValue("tlabSize").longValue() == (DEFAULT_ALIGNED_HEAP_CHUNK_SIZE) && + event. getValue("objectClass").getName().equals(byte[].class.getName())) { + foundSmallByte = true; + } else if (event. getValue("tlabSize").longValue() == (DEFAULT_ALIGNED_HEAP_CHUNK_SIZE) && event. getValue("objectClass").getName().equals(Helper.class.getName())) { + foundInstance = true; + } + } + if (!foundBigChar || !foundBigByte || !foundSmallByte || !foundInstance) { + assertTrue("Expected events not found. foundBigChar: " + foundBigChar + " foundBigByte:" + foundBigByte + " foundSmallByte:" + foundSmallByte + " foundInstance:" + foundInstance, + foundBigChar && foundBigByte && foundSmallByte && foundInstance); + } + } + + @Test + public void test() throws Exception { + + // These arrays must result in exceeding the large array threshold, resulting in new TLABs. Big Byte. + byte[] bigByte = new byte[2 * DEFAULT_ALIGNED_HEAP_CHUNK_SIZE]; + Arrays.fill(bigByte, (byte) 0); + + // Using char, so it's the same size as bigByte. Big Char. + char[] bigChar = new char[DEFAULT_ALIGNED_HEAP_CHUNK_SIZE]; + Arrays.fill(bigChar, 'm'); + + // Try to exhaust TLAB with small arrays. Small byte. + for (int i = 0; i < DEFAULT_ALIGNED_HEAP_CHUNK_SIZE / KILO; i++) { + byteArray = new byte[KILO]; + Arrays.fill(byteArray, (byte) 0); + } + + // Try to exhaust TLAB with instances. Instance. + for (int i = 0; i < DEFAULT_ALIGNED_HEAP_CHUNK_SIZE; i++) { + helper = new Helper(); + } + } + + /** + * This class is only needed to provide a unique name in the event's "objectClass" field that we + * check. + */ + static class Helper { + int testField; + } +}