Skip to content
1 change: 1 addition & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ This changelog summarizes major changes to GraalVM Native Image.

## GraalVM for JDK 22 (Internal Version 24.0.0)
* (GR-48304) Red Hat added support for the JFR event ThreadAllocationStatistics.
* (GR-48343) Red Hat added support for the JFR events AllocationRequiringGC and SystemGC.

## GraalVM for JDK 21 (Internal Version 23.1.0)
* (GR-35746) Lower the default aligned chunk size from 1 MB to 512 KB for the serial and epsilon GCs, reducing memory usage and image size in many cases.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@

import com.oracle.svm.core.SubstrateGCOptions;
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.heap.GCCause;
import com.oracle.svm.core.heap.PhysicalMemory;
import com.oracle.svm.core.heap.ReferenceAccess;
import com.oracle.svm.core.jdk.UninterruptibleUtils;
Expand Down Expand Up @@ -96,8 +95,9 @@ public boolean shouldCollectOnAllocation() {
}

@Override
public boolean shouldCollectOnRequest(GCCause cause, boolean fullGC) {
return cause == GCCause.JavaLangSystemGC && !SubstrateGCOptions.DisableExplicitGC.getValue();
public boolean shouldCollectOnHint(boolean fullGC) {
/* Collection hints are not supported. */
return false;
}

@Fold
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ public boolean shouldCollectOnAllocation() {
}

@Override
public boolean shouldCollectOnRequest(GCCause cause, boolean fullGC) {
return cause == GCCause.JavaLangSystemGC && !SubstrateGCOptions.DisableExplicitGC.getValue();
public boolean shouldCollectOnHint(boolean fullGC) {
/* Collection hints are not supported. */
return false;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,10 @@ static boolean shouldCollectYoungGenSeparately(boolean defaultValue) {
boolean shouldCollectOnAllocation();

/**
* Return true if a user-requested GC (e.g., call to {@link System#gc()} or
* {@link org.graalvm.compiler.serviceprovider.GraalServices#notifyLowMemoryPoint(boolean)})
* should be performed.
* Called when an application provides a hint to the GC that it might be a good time to do a
* collection. Returns true if the GC decides to do a collection.
*/
boolean shouldCollectOnRequest(GCCause cause, boolean fullGC);
boolean shouldCollectOnHint(boolean fullGC);

/**
* At a safepoint, decides whether to do a complete collection (returning {@code true}) or an
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public long getIncrementalCollectionTotalNanos() {
return incrementalCollectionTotalNanos;
}

@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public long getCompleteCollectionCount() {
return completeCollectionCount;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import com.oracle.svm.core.jdk.RuntimeSupport;
import com.oracle.svm.core.jfr.JfrGCWhen;
import com.oracle.svm.core.jfr.JfrTicks;
import com.oracle.svm.core.jfr.events.AllocationRequiringGCEvent;
import com.oracle.svm.core.log.Log;
import com.oracle.svm.core.os.CommittedMemoryProvider;
import com.oracle.svm.core.snippets.ImplicitExceptions;
Expand Down Expand Up @@ -144,12 +145,13 @@ public void collect(GCCause cause) {
collect(cause, false);
}

public void maybeCollectOnAllocation() {
public void maybeCollectOnAllocation(UnsignedWord allocationSize) {
boolean outOfMemory = false;
if (hasNeverCollectPolicy()) {
UnsignedWord edenUsed = HeapImpl.getAccounting().getEdenUsedBytes();
outOfMemory = edenUsed.aboveThan(GCImpl.getPolicy().getMaximumHeapSize());
} else if (getPolicy().shouldCollectOnAllocation()) {
AllocationRequiringGCEvent.emit(getCollectionEpoch(), allocationSize);
outOfMemory = collectWithoutAllocating(GenScavengeGCCause.OnAllocation, false);
}
if (outOfMemory) {
Expand All @@ -158,9 +160,9 @@ public void maybeCollectOnAllocation() {
}

@Override
public void maybeCauseUserRequestedCollection(GCCause cause, boolean fullGC) {
if (policy.shouldCollectOnRequest(cause, fullGC)) {
collect(cause, fullGC);
public void collectionHint(boolean fullGC) {
if (policy.shouldCollectOnHint(fullGC)) {
collect(GCCause.HintedGC, fullGC);
}
}

Expand All @@ -183,6 +185,7 @@ boolean collectWithoutAllocating(GCCause cause, boolean forceFullGC) {
UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0);
data.setCauseId(cause.getId());
data.setRequestingEpoch(getCollectionEpoch());
data.setCompleteCollectionCount(GCImpl.getAccounting().getCompleteCollectionCount());
data.setRequestingNanoTime(System.nanoTime());
data.setForceFullGC(forceFullGC);
enqueueCollectOperation(data);
Expand All @@ -197,7 +200,8 @@ private void enqueueCollectOperation(CollectionVMOperationData data) {
/** The body of the VMOperation to do the collection. */
private void collectOperation(CollectionVMOperationData data) {
assert VMOperation.isGCInProgress();
assert getCollectionEpoch().equal(data.getRequestingEpoch());
assert getCollectionEpoch().equal(data.getRequestingEpoch()) ||
data.getForceFullGC() && GCImpl.getAccounting().getCompleteCollectionCount() == data.getCompleteCollectionCount() : "unnecessary GC?";

timers.mutator.closeAt(data.getRequestingNanoTime());
timers.resetAllExceptMutator();
Expand Down Expand Up @@ -1205,7 +1209,12 @@ private static void collect(CollectionVMOperationData data) {
@Override
protected boolean hasWork(NativeVMOperationData data) {
CollectionVMOperationData d = (CollectionVMOperationData) data;
return HeapImpl.getGCImpl().getCollectionEpoch().equal(d.getRequestingEpoch());
if (d.getForceFullGC()) {
/* Skip if another full GC happened in the meanwhile. */
return GCImpl.getAccounting().getCompleteCollectionCount() == d.getCompleteCollectionCount();
}
/* Skip if any other GC happened in the meanwhile. */
return GCImpl.getGCImpl().getCollectionEpoch().equal(d.getRequestingEpoch());
}
}

Expand Down Expand Up @@ -1235,6 +1244,12 @@ private interface CollectionVMOperationData extends NativeVMOperationData {
@RawField
void setForceFullGC(boolean value);

@RawField
long getCompleteCollectionCount();

@RawField
void setCompleteCollectionCount(long value);

@RawField
boolean getOutOfMemory();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import com.oracle.svm.core.SubstrateDiagnostics.DiagnosticThunk;
import com.oracle.svm.core.SubstrateDiagnostics.DiagnosticThunkRegistry;
import com.oracle.svm.core.SubstrateDiagnostics.ErrorContext;
import com.oracle.svm.core.SubstrateGCOptions;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.Uninterruptible;
Expand All @@ -73,6 +74,8 @@
import com.oracle.svm.core.heap.RuntimeCodeInfoGCSupport;
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicReference;
import com.oracle.svm.core.jfr.JfrTicks;
import com.oracle.svm.core.jfr.events.SystemGCEvent;
import com.oracle.svm.core.locks.VMCondition;
import com.oracle.svm.core.locks.VMMutex;
import com.oracle.svm.core.log.Log;
Expand Down Expand Up @@ -941,6 +944,10 @@ private long maxMemory() {

@Substitute
private void gc() {
GCImpl.getGCImpl().maybeCauseUserRequestedCollection(GCCause.JavaLangSystemGC, true);
if (!SubstrateGCOptions.DisableExplicitGC.getValue()) {
long startTicks = JfrTicks.elapsedTicks();
GCImpl.getGCImpl().collectCompletely(GCCause.JavaLangSystemGC);
SystemGCEvent.emit(startTicks, false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,18 @@ public static final class Options {
* size is lower but the hinted GC is more often.
*/
@Override
public boolean shouldCollectOnRequest(GCCause cause, boolean fullGC) {
if (cause == GCCause.HintedGC) {
guaranteeSizeParametersInitialized();
UnsignedWord edenUsedBytes = HeapImpl.getAccounting().getEdenUsedBytes();
if (fullGC) {
// For full GC request, we slightly lower the threshold to increase their
// probability to be performed, as they are supposed to be issued at the lowest
// memory usage point.
edenUsedBytes = edenUsedBytes.add(FULL_GC_BONUS);
}
return edenUsedBytes.aboveOrEqual(WordFactory.unsigned(Options.ExpectedEdenSize.getValue())) ||
(UnsignedUtils.toDouble(edenUsedBytes) / UnsignedUtils.toDouble(edenSize) >= Options.UsedEdenProportionThreshold.getValue());
public boolean shouldCollectOnHint(boolean fullGC) {
guaranteeSizeParametersInitialized();
UnsignedWord edenUsedBytes = HeapImpl.getAccounting().getEdenUsedBytes();
if (fullGC) {
/*
* For full GC request, we slightly lower the threshold to increase their probability to
* be performed, as they are supposed to be issued at the lowest memory usage point.
*/
edenUsedBytes = edenUsedBytes.add(FULL_GC_BONUS);
}
return super.shouldCollectOnRequest(cause, fullGC);
return edenUsedBytes.aboveOrEqual(WordFactory.unsigned(Options.ExpectedEdenSize.getValue())) ||
(UnsignedUtils.toDouble(edenUsedBytes) / UnsignedUtils.toDouble(edenSize) >= Options.UsedEdenProportionThreshold.getValue());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,14 +230,15 @@ private static Object slowPathNewInstance(Word objectHeader) {
private static Object slowPathNewInstanceWithoutAllocating(DynamicHub hub) {
DeoptTester.disableDeoptTesting();
long startTicks = JfrTicks.elapsedTicks();
UnsignedWord size = LayoutEncoding.getPureInstanceAllocationSize(hub.getLayoutEncoding());
try {
HeapImpl.exitIfAllocationDisallowed("ThreadLocalAllocation.slowPathNewInstanceWithoutAllocating", DynamicHub.toClass(hub).getName());
GCImpl.getGCImpl().maybeCollectOnAllocation();
GCImpl.getGCImpl().maybeCollectOnAllocation(size);

AlignedHeader newTlab = HeapImpl.getChunkProvider().produceAlignedChunk();
return allocateInstanceInNewTlab(hub, newTlab);
return allocateInstanceInNewTlab(hub, size, newTlab);
} finally {
ObjectAllocationInNewTLABEvent.emit(startTicks, hub, LayoutEncoding.getPureInstanceAllocationSize(hub.getLayoutEncoding()), HeapParameters.getAlignedHeapChunkSize());
ObjectAllocationInNewTLABEvent.emit(startTicks, hub, size, HeapParameters.getAlignedHeapChunkSize());
DeoptTester.enableDeoptTesting();
}
}
Expand Down Expand Up @@ -296,7 +297,7 @@ private static Object slowPathNewArrayLikeObject0(DynamicHub hub, int length, Un
UnsignedWord tlabSize = HeapParameters.getAlignedHeapChunkSize();
try {
HeapImpl.exitIfAllocationDisallowed("ThreadLocalAllocation.slowPathNewArrayOrPodWithoutAllocating", DynamicHub.toClass(hub).getName());
GCImpl.getGCImpl().maybeCollectOnAllocation();
GCImpl.getGCImpl().maybeCollectOnAllocation(size);

if (size.aboveOrEqual(HeapParameters.getLargeArrayThreshold())) {
/* Large arrays go into their own unaligned chunk. */
Expand All @@ -305,10 +306,12 @@ private static Object slowPathNewArrayLikeObject0(DynamicHub hub, int length, Un
tlabSize = UnalignedHeapChunk.getChunkSizeForObject(size);
return allocateLargeArrayLikeObjectInNewTlab(hub, length, size, newTlabChunk, needsZeroing, podReferenceMap);
}
/* Small arrays go into the regular aligned chunk. */

// We might have allocated in the caller and acquired a TLAB with enough space already
// (but we need to check in an uninterruptible method to be safe)
/*
* Small arrays go into the regular aligned chunk. We might have allocated in the caller
* and acquired a TLAB with enough space already (but we need to check in an
* uninterruptible method to be safe).
*/
Object array = allocateSmallArrayLikeObjectInCurrentTlab(hub, length, size, podReferenceMap);
if (array == null) { // We need a new chunk.
AlignedHeader newTlabChunk = HeapImpl.getChunkProvider().produceAlignedChunk();
Expand All @@ -322,8 +325,8 @@ private static Object slowPathNewArrayLikeObject0(DynamicHub hub, int length, Un
}

@Uninterruptible(reason = "Holds uninitialized memory.")
private static Object allocateInstanceInNewTlab(DynamicHub hub, AlignedHeader newTlabChunk) {
UnsignedWord size = LayoutEncoding.getPureInstanceAllocationSize(hub.getLayoutEncoding());
private static Object allocateInstanceInNewTlab(DynamicHub hub, UnsignedWord size, AlignedHeader newTlabChunk) {
assert size.equal(LayoutEncoding.getPureInstanceAllocationSize(hub.getLayoutEncoding()));
Pointer memory = allocateRawMemoryInNewTlab(size, newTlabChunk);
return FormatObjectNode.formatObject(memory, DynamicHub.toClass(hub), false, FillContent.WITH_ZEROES, true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ public interface GC {
/** Cause a full collection. */
void collectCompletely(GCCause cause);

/**
* Notify the GC that it might be a good time to do a collection. The final decision is up to
* the GC and its policy.
*/
void collectionHint(boolean fullGC);

/** Human-readable name. */
String getName();

/** Human-readable default heap size. */
String getDefaultMaxHeapSize();

/** Issue an optional GC request. */
default void maybeCauseUserRequestedCollection(@SuppressWarnings("unused") GCCause cause, @SuppressWarnings("unused") boolean fullGC) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public final class JfrEvent {
public static final JfrEvent ObjectAllocationInNewTLAB = create("jdk.ObjectAllocationInNewTLAB", false);
public static final JfrEvent GCHeapSummary = create("jdk.GCHeapSummary", false);
public static final JfrEvent ThreadAllocationStatistics = create("jdk.ThreadAllocationStatistics", false);
public static final JfrEvent SystemGC = create("jdk.SystemGC", true);
public static final JfrEvent AllocationRequiringGC = create("jdk.AllocationRequiringGC", false);

private final long id;
private final String name;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.jfr.events;

import org.graalvm.nativeimage.StackValue;
import org.graalvm.word.UnsignedWord;

import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.jfr.HasJfrSupport;
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.JfrTicks;
import com.oracle.svm.core.jfr.SubstrateJVM;

public class AllocationRequiringGCEvent {
public static void emit(UnsignedWord gcId, UnsignedWord size) {
if (HasJfrSupport.get()) {
emit0(gcId, size);
}
}

@Uninterruptible(reason = "Accesses a JFR buffer.")
private static void emit0(UnsignedWord gcId, UnsignedWord size) {
if (JfrEvent.AllocationRequiringGC.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);

JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.AllocationRequiringGC);
JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks());
JfrNativeEventWriter.putEventThread(data);
JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.AllocationRequiringGC, 0));
JfrNativeEventWriter.putLong(data, gcId.rawValue());
JfrNativeEventWriter.putLong(data, size.rawValue());
JfrNativeEventWriter.endSmallEvent(data);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ private static void emit0(long startTicks, Object obj, long notifier, long timeo
if (JfrEvent.JavaMonitorWait.shouldEmit(duration)) {
JfrNativeEventWriterData data = org.graalvm.nativeimage.StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);

JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.JavaMonitorWait);
JfrNativeEventWriter.putLong(data, startTicks);
JfrNativeEventWriter.putLong(data, duration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ private static void emit0(long startTicks, DynamicHub hub, UnsignedWord allocati
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);
Expand Down
Loading