diff --git a/src/hotspot/share/gc/z/zPageAllocator.cpp b/src/hotspot/share/gc/z/zPageAllocator.cpp index 80a53feed71..52c8c5af77a 100644 --- a/src/hotspot/share/gc/z/zPageAllocator.cpp +++ b/src/hotspot/share/gc/z/zPageAllocator.cpp @@ -1936,7 +1936,7 @@ void ZPageAllocator::cleanup_failed_commit_multi_partition(ZMultiPartitionAlloca } const size_t committed = allocation->committed_capacity(); - const ZVirtualMemory non_harvested_vmem = vmem.last_part(allocation->harvested()); + const ZVirtualMemory non_harvested_vmem = partial_vmem.last_part(allocation->harvested()); const ZVirtualMemory committed_vmem = non_harvested_vmem.first_part(committed); const ZVirtualMemory non_committed_vmem = non_harvested_vmem.last_part(committed); diff --git a/src/hotspot/share/gc/z/zPhysicalMemoryManager.cpp b/src/hotspot/share/gc/z/zPhysicalMemoryManager.cpp index 2c2f9988d4b..54477d19a27 100644 --- a/src/hotspot/share/gc/z/zPhysicalMemoryManager.cpp +++ b/src/hotspot/share/gc/z/zPhysicalMemoryManager.cpp @@ -214,9 +214,20 @@ void ZPhysicalMemoryManager::free(const ZVirtualMemory& vmem, uint32_t numa_id) }); } +static size_t inject_commit_limit(const ZVirtualMemory& vmem) { + // To facilitate easier interoperability with multi partition allocations we + // divide by ZNUMA::count(). Users of ZFailLargerCommits need to be aware of + // this when writing tests. In the future we could probe the VirtualMemoryManager + // and condition this division on whether the vmem is in the multi partition + // address space. + return align_up(MIN2(ZFailLargerCommits / ZNUMA::count(), vmem.size()), ZGranuleSize); +} + size_t ZPhysicalMemoryManager::commit(const ZVirtualMemory& vmem, uint32_t numa_id) { zbacking_index* const pmem = _physical_mappings.addr(vmem.start()); - const size_t size = vmem.size(); + const size_t size = ZFailLargerCommits > 0 + ? inject_commit_limit(vmem) + : vmem.size(); size_t total_committed = 0; diff --git a/src/hotspot/share/gc/z/z_globals.hpp b/src/hotspot/share/gc/z/z_globals.hpp index e1f525e5372..4e5d725a5fd 100644 --- a/src/hotspot/share/gc/z/z_globals.hpp +++ b/src/hotspot/share/gc/z/z_globals.hpp @@ -118,6 +118,11 @@ develop(bool, ZVerifyOops, false, \ "Verify accessed oops") \ \ + develop(size_t, ZFailLargerCommits, 0, \ + "Commits larger than ZFailLargerCommits will be truncated, " \ + "used to stress page allocation commit failure paths " \ + "(0: Disabled)") \ + \ develop(uint, ZFakeNUMA, 1, \ "ZFakeNUMA is used to test the internal NUMA memory support " \ "without the need for UseNUMA") \ diff --git a/test/hotspot/jtreg/gc/z/TestCommitFailure.java b/test/hotspot/jtreg/gc/z/TestCommitFailure.java new file mode 100644 index 00000000000..8d3367f37b5 --- /dev/null +++ b/test/hotspot/jtreg/gc/z/TestCommitFailure.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. 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. + * + * 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 gc.z; + +/* + * @test id=ZFakeNUMA + * @requires vm.gc.Z & vm.debug + * @library / /test/lib + * @summary Test ZGC graceful failure when a commit fails (with ZFakeNUMA) + * @run driver gc.z.TestCommitFailure -XX:ZFakeNUMA=16 + */ + +import jdk.test.lib.process.ProcessTools; + +import static gc.testlibrary.Allocation.blackHole; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class TestCommitFailure { + static final int K = 1024; + static final int M = 1024 * K; + + static final int XMS = 128 * M; + static final int XMX = 512 * M; + + static class Test { + static final int LARGE_ALLOC = 256 * M; + static final int SMALL_GARBAGE = 256 * M; + static final int SMALL_LIVE = 128 * M; + + // Allocates at least totalLive bytes of objects and add them to list. + static void allocLive(List list, int totalLive) { + final int largePageAllocationSize = 6 * M; + for (int live = 0; live < totalLive; live += largePageAllocationSize) { + list.add(new byte[largePageAllocationSize - K]); + } + } + + // Allocates at least totalGarbage bytes of garbage large pages. + static void allocGarbage(int totalGarbage) { + final int largePageAllocationSize = 6 * M; + for (int garbage = 0; garbage < totalGarbage; garbage += largePageAllocationSize) { + blackHole(new byte[largePageAllocationSize - K]); + } + } + + public static void main(String[] args) { + final var list = new ArrayList(); + try { + // Fill heap with small live objects + allocLive(list, SMALL_LIVE); + // Fill with small garbage objects + allocGarbage(SMALL_GARBAGE); + // Allocate large objects where commit fails until an OOME is thrown + while (true) { + list.add(new byte[LARGE_ALLOC - K]); + } + } catch (OutOfMemoryError oome) {} + blackHole(list); + } + } + + public static void main(String[] args) throws Exception { + final int xmxInM = XMX / M; + final int xmsInM = XMS / M; + final var arguments = new ArrayList(Arrays.asList(args)); + arguments.addAll(List.of( + "-XX:+UseZGC", + "-Xlog:gc+init", + "-XX:ZFailLargerCommits=" + XMS, + "-Xms" + xmsInM + "M", + "-Xmx" + xmxInM + "M", + Test.class.getName())); + + ProcessTools.executeTestJava(arguments) + .outputTo(System.out) + .errorTo(System.out) + .shouldHaveExitValue(0); + } +}