diff --git a/src/hotspot/share/gc/shared/gcConfig.cpp b/src/hotspot/share/gc/shared/gcConfig.cpp index 506b368d6cf05..c9d4bc8ae4517 100644 --- a/src/hotspot/share/gc/shared/gcConfig.cpp +++ b/src/hotspot/share/gc/shared/gcConfig.cpp @@ -95,7 +95,7 @@ void GCConfig::fail_if_non_included_gc_is_selected() { NOT_ZGC( FAIL_IF_SELECTED(UseZGC)); } -void GCConfig::select_gc_ergonomically() { +void select_gc_ergonomically_shared() { if (os::is_server_class_machine()) { #if INCLUDE_G1GC FLAG_SET_ERGO_IF_DEFAULT(UseG1GC, true); @@ -111,6 +111,40 @@ void GCConfig::select_gc_ergonomically() { } } +// Selects the garbage collector (GC) ergonomically based on the system's characteristics. +// It first checks the physical memory available on the system and the number of active processors. +// Depending on these factors, it sets the appropriate GC flag to true using the FLAG_SET_ERGO_IF_DEFAULT macro. +// If the system has only one active processor, it selects the Serial GC, no matter how much memory is available. +// If the physical memory is greater than 2GB, it selects the G1GC. +// Otherwise, it selects the Parallel GC. +void select_gc_ergonomically_dedicated() { + julong phys_mem = os::physical_memory(); + if (os::active_processor_count() <= 1) { +#if INCLUDE_SERIALGC + FLAG_SET_ERGO_IF_DEFAULT(UseSerialGC, true); +#endif + } else if (phys_mem > 2048*M) { +#if INCLUDE_G1GC + FLAG_SET_ERGO_IF_DEFAULT(UseG1GC, true); +#endif + } else { +#if INCLUDE_PARALLELGC + FLAG_SET_ERGO_IF_DEFAULT(UseParallelGC, true); +#endif + } +} + +void GCConfig::select_gc_ergonomically() { + if (strcmp(ErgonomicsProfile, "shared") == 0) { + select_gc_ergonomically_shared(); + } else if (strcmp(ErgonomicsProfile, "dedicated") == 0) { + select_gc_ergonomically_dedicated(); + } else { + // We have got to have at least one GC selected. Otherwise, we will crash. + ShouldNotReachHere(); + } +} + bool GCConfig::is_no_gc_selected() { FOR_EACH_INCLUDED_GC(gc) { if (gc->_flag) { diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp index af735a865dcb4..5632baf7f384f 100644 --- a/src/hotspot/share/gc/shared/gc_globals.hpp +++ b/src/hotspot/share/gc/shared/gc_globals.hpp @@ -124,6 +124,14 @@ product(bool, UseShenandoahGC, false, \ "Use the Shenandoah garbage collector") \ \ + /* notice: once stable enough, goal is to change default to auto */ \ + product(bool, AutoErgonomicsProfile, false, "Use automatic selection of " \ + "ergonomics profiles.") \ + product(ccstr, ErgonomicsProfile, "shared", \ + "Ergonomics profile to use. " \ + "\"shared\" for traditional environments (default). " \ + "\"dedicated\" for environments with dedicated resources.") \ + \ /* notice: the max range value here is INT_MAX not UINT_MAX */ \ /* to protect from overflows */ \ product(uint, ParallelGCThreads, 0, \ diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 802ddee756f2b..5250fd036b48c 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -72,6 +72,9 @@ #if INCLUDE_JFR #include "jfr/jfr.hpp" #endif +#ifdef LINUX +#include "osContainer_linux.hpp" +#endif #include @@ -1438,6 +1441,18 @@ void Arguments::set_use_compressed_klass_ptrs() { #endif // _LP64 } +static void validate_ergonomics_profile() { + if (AutoErgonomicsProfile && FLAG_IS_CMDLINE(ErgonomicsProfile)) { + vm_exit_during_initialization(err_msg("Automatic ergonomics profile selection is enabled. Do not set a specific profile at the same time.")); + } + + if (FLAG_IS_CMDLINE(ErgonomicsProfile) && + strcmp(ErgonomicsProfile, "shared") != 0 && + strcmp(ErgonomicsProfile, "dedicated") != 0) { + vm_exit_during_initialization(err_msg("Unsupported ErgonomicsProfile: %s", ErgonomicsProfile)); + } +} + void Arguments::set_conservative_max_heap_alignment() { // The conservative maximum required alignment for the heap is the maximum of // the alignments imposed by several sources: any requirements from the heap @@ -1465,6 +1480,30 @@ jint Arguments::set_ergonomics_flags() { return JNI_OK; } +void Arguments::set_ergonomics_profile() { + validate_ergonomics_profile(); + + // Check if the value is 'auto'. + if (AutoErgonomicsProfile) { + // Set the ergonomics profile based on platform. + // If it is a Linux environment, check if we are inside a container. + // If yes, we apply dedicated automatically. + // May support other platforms in the future (e.g. Windows Containers, Solaris Zones, FreeBSD Jails, Virtuozzo OpenVZ, etc). + #ifdef LINUX + if (OSContainer::is_containerized()){ + FLAG_SET_ERGO(ErgonomicsProfile, "dedicated"); + } + #endif //LINUX + + if (FLAG_IS_DEFAULT(ErgonomicsProfile)) { + FLAG_SET_ERGO(ErgonomicsProfile, "shared"); + } + } + + // Store so we can expose through JMX RuntimeMBean + PropertyList_add(&_system_properties, new SystemProperty("java.vm.ergonomics.profile", ErgonomicsProfile, false)); +} + size_t Arguments::limit_heap_by_allocatable_memory(size_t limit) { size_t max_allocatable; size_t result = limit; @@ -1509,6 +1548,8 @@ void Arguments::set_heap_size() { : (julong)MaxRAM; } + set_ergonomics_profiles_heap_size_max_ram_percentage(phys_mem); + // If the maximum heap size has not been set with -Xmx, // then set it as fraction of the size of physical memory, // respecting the maximum and minimum sizes of the heap. @@ -1616,6 +1657,25 @@ void Arguments::set_heap_size() { } } +void Arguments::set_ergonomics_profiles_heap_size_max_ram_percentage(julong phys_mem) { + // Update default heap size for dedicated ergonomics profile + if (strcmp(ErgonomicsProfile, "dedicated") == 0) { + FLAG_SET_ERGO_IF_DEFAULT(InitialRAMPercentage, 50.0); + + if (phys_mem >= 16 * G) { + FLAG_SET_ERGO_IF_DEFAULT(MaxRAMPercentage, 90.0); + } else if (phys_mem >= 6 * G) { + FLAG_SET_ERGO_IF_DEFAULT(MaxRAMPercentage, 85.0); + } else if (phys_mem >= 4 * G) { + FLAG_SET_ERGO_IF_DEFAULT(MaxRAMPercentage, 80.0); + } else if (phys_mem >= 0.5 * G) { + FLAG_SET_ERGO_IF_DEFAULT(MaxRAMPercentage, 75.0); + } else { + FLAG_SET_ERGO_IF_DEFAULT(MaxRAMPercentage, 50.0); + } + } +} + // This option inspects the machine and attempts to set various // parameters to be optimal for long-running, memory allocation // intensive jobs. It is intended for machines with large @@ -3654,6 +3714,9 @@ jint Arguments::parse(const JavaVMInitArgs* initial_cmd_args) { } jint Arguments::apply_ergo() { + // Set the ergonomics profile + set_ergonomics_profile(); + // Set flags based on ergonomics. jint result = set_ergonomics_flags(); if (result != JNI_OK) return result; diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp index 8251db3d0d59a..0b48bde764d19 100644 --- a/src/hotspot/share/runtime/arguments.hpp +++ b/src/hotspot/share/runtime/arguments.hpp @@ -259,6 +259,7 @@ class Arguments : AllStatic { static bool _ClipInlining; // GC ergonomics + static void set_ergonomics_profile(); static void set_conservative_max_heap_alignment(); static void set_use_compressed_oops(); static void set_use_compressed_klass_ptrs(); @@ -270,6 +271,8 @@ class Arguments : AllStatic { // Setup heap size static void set_heap_size(); + static void set_ergonomics_profiles_heap_size_max_ram_percentage(julong phys_mem); + // Bytecode rewriting static void set_bytecode_flags(); diff --git a/src/java.management/share/classes/java/lang/management/RuntimeMXBean.java b/src/java.management/share/classes/java/lang/management/RuntimeMXBean.java index 2318a660c9d4e..63f7f878d26c8 100644 --- a/src/java.management/share/classes/java/lang/management/RuntimeMXBean.java +++ b/src/java.management/share/classes/java/lang/management/RuntimeMXBean.java @@ -359,4 +359,16 @@ public default long getPid() { * to the system properties. */ public java.util.Map getSystemProperties(); + + /** + * Returns the selected ergonomics profile for this Java virtual machine. + * The profile will be one of the following: + * + * + * @return the name of the selected ergonomics profile + */ + public String getJvmErgonomicsProfile(); } diff --git a/src/java.management/share/classes/sun/management/RuntimeImpl.java b/src/java.management/share/classes/sun/management/RuntimeImpl.java index c919c0eeddfa2..f13d075402af3 100644 --- a/src/java.management/share/classes/sun/management/RuntimeImpl.java +++ b/src/java.management/share/classes/sun/management/RuntimeImpl.java @@ -133,6 +133,10 @@ public Map getSystemProperties() { return map; } + public String getJvmErgonomicsProfile() { + return jvm.getErgonomicsProfile(); + } + public ObjectName getObjectName() { return Util.newObjectName(ManagementFactory.RUNTIME_MXBEAN_NAME); } diff --git a/src/java.management/share/classes/sun/management/VMManagement.java b/src/java.management/share/classes/sun/management/VMManagement.java index f4445f0225af3..b559c2f9cd97e 100644 --- a/src/java.management/share/classes/sun/management/VMManagement.java +++ b/src/java.management/share/classes/sun/management/VMManagement.java @@ -73,6 +73,7 @@ public interface VMManagement { public long getStartupTime(); public long getUptime(); public int getAvailableProcessors(); + public String getErgonomicsProfile(); // Compilation Subsystem public String getCompilerName(); diff --git a/src/java.management/share/classes/sun/management/VMManagementImpl.java b/src/java.management/share/classes/sun/management/VMManagementImpl.java index 4e2fc26c850f5..7a5c64241f412 100644 --- a/src/java.management/share/classes/sun/management/VMManagementImpl.java +++ b/src/java.management/share/classes/sun/management/VMManagementImpl.java @@ -201,6 +201,10 @@ public synchronized List getVmArguments() { private native long getUptime0(); public native int getAvailableProcessors(); + public String getErgonomicsProfile() { + return System.getProperty("java.vm.ergonomics.profile"); + } + // Compilation Subsystem public String getCompilerName() { @SuppressWarnings("removal") diff --git a/test/hotspot/jtreg/gc/ergonomics/TestErgonomicsProfiles.java b/test/hotspot/jtreg/gc/ergonomics/TestErgonomicsProfiles.java new file mode 100644 index 0000000000000..8e08ee3193302 --- /dev/null +++ b/test/hotspot/jtreg/gc/ergonomics/TestErgonomicsProfiles.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2015, 2023, 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.ergonomics; + +/* + * @test TestErgonomicsProfiles + * @bug 8017462 + * @summary Ensure that ErgonomicsProfiles selects the desired profile + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI gc.ergonomics.TestErgonomicsProfiles + */ + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import jdk.test.lib.containers.docker.Common; +import jdk.test.lib.containers.docker.DockerRunOptions; +import jdk.test.lib.containers.docker.DockerTestUtils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jdk.test.lib.Platform; +import jdk.test.lib.Utils; + +import jtreg.SkippedException; +import jdk.test.whitebox.gc.GC; + +/* + * @test TestErgonomicsProfiles + * @summary Ensure that ErgonomicsProfile fails with unsupported profile + * @requires docker.support + * @modules java.base/jdk.internal.misc + * java.management + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI gc.ergonomics.TestErgonomicsProfiles + */ +public class TestErgonomicsProfiles { + + private final static String imageNameAndTag = "ergoimage:latest"; + + private static final String DEDICATED_PROFILE = "dedicated"; + private static final String SHARED_PROFILE = "shared"; + private static final String AUTO_SELECTION = "auto"; + + public static void main(String[] args) throws Exception { + testInvalidProfile(); + + // Test manual selection + testErgonomicProfile(DEDICATED_PROFILE, DEDICATED_PROFILE); + testErgonomicProfile(SHARED_PROFILE, SHARED_PROFILE); + + testDefaultErgonomicProfile(SHARED_PROFILE); // default profile is shared. Change if default changes. + + // Tests inside containers + try { + // Prepare container image + prepareContainerImage(); + + // Test automatic selection and default, inside container + testProfileInsideContainer(AUTO_SELECTION, DEDICATED_PROFILE); + + // Test GC selection + // See GC selection in gcConfig.cpp::select_gc_ergonomically_dedicated + testGCSelection(DEDICATED_PROFILE, "UseSerialGC", 1, 1024); // no matter how much memory, use serial if 1 proc + testGCSelection(DEDICATED_PROFILE, "UseParallelGC", 2, 1024); // <=2G, use Parallel + testGCSelection(DEDICATED_PROFILE, "UseG1GC", 2, 2 * 1024 + 1); // above >2GB, use G1 + + // Test Heap Size allocation (MaxRAMPercentage) + // See heap size MaxRAMPercentage ergo selection for dedicated in + // arguments.cpp::set_ergonomics_profiles_heap_size_max_ram_percentage + // Test MaxRAMPercentage selection for dedicated profile + testMaxRAMPercentageSelection(DEDICATED_PROFILE, "50.0", 511); // less than 0.5G, MaxRAMPercentage should be 50.0 + // MaxRAMPercentage should be 75.0 between => 0.5G to <4G + testMaxRAMPercentageSelection(DEDICATED_PROFILE, "75.0", 512); + testMaxRAMPercentageSelection(DEDICATED_PROFILE, "75.0", 4 * 1024 - 1); + // MaxRAMPercentage should be 80.0 between => 4G to <6G + testMaxRAMPercentageSelection(DEDICATED_PROFILE, "80.0", 4 * 1024); + testMaxRAMPercentageSelection(DEDICATED_PROFILE, "80.0", 6 * 1024 - 1); + // MaxRAMPercentage should be 85.0 between => 6G to <16G + testMaxRAMPercentageSelection(DEDICATED_PROFILE, "85.0", 6 * 1024); + testMaxRAMPercentageSelection(DEDICATED_PROFILE, "85.0", 16 * 1024 - 1); + // MaxRAMPercentage should be 90.0 between => 16G or more + // testMaxRAMPercentageSelection(DEDICATED_PROFILE, "90.0", 16 * 1024); + } finally { + if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) { + DockerTestUtils.removeDockerImage(imageNameAndTag); + } + } + } + + private static void testInvalidProfile() throws Exception { + String[] baseArgs = { "-XX:ErgonomicsProfile=invalid", "-XX:+PrintFlagsFinal", "-version" }; + + // Base test with invalid ergonomics profile + OutputAnalyzer output = ProcessTools.executeLimitedTestJava(baseArgs); + output + .shouldHaveExitValue(1) + .stderrContains("Unsupported ErgonomicsProfile: invalid"); + } + + private static void testDefaultErgonomicProfile(String expectedProfile) throws Exception { + String[] baseArgs = { "-XX:+PrintFlagsFinal", "-version" }; + + // Base test with default ergonomics profile + ProcessTools.executeLimitedTestJava(baseArgs) + .shouldHaveExitValue(0) + .stdoutShouldMatch("ErgonomicsProfile.*" + expectedProfile); + } + + private static void testErgonomicProfile(String ergonomicsProfile, String expectedProfile) throws Exception { + String[] baseArgs = { "-XX:ErgonomicsProfile=" + ergonomicsProfile, "-XX:+PrintFlagsFinal", "-version" }; + + // Base test with selected ergonomics profile + ProcessTools.executeLimitedTestJava(baseArgs) + .shouldHaveExitValue(0) + .stdoutShouldMatch("ErgonomicsProfile.*" + expectedProfile); + } + + private static void prepareContainerImage() throws Exception { + if (!DockerTestUtils.canTestDocker()) { + System.out.println("Docker not available, skipping test"); + throw new SkippedException("Docker not available"); + } + + String dockerfile = "FROM --platform=linux/amd64 ubuntu:latest\n" + + "COPY /jdk /jdk\n" + + "ENV JAVA_HOME=/jdk\n" + + "CMD [\"/bin/bash\"]\n"; + + DockerTestUtils.buildJdkContainerImage(imageNameAndTag, dockerfile); + } + + private static void testProfileInsideContainer(String ergonomicsProfile, String expectedProfile) throws Exception { + String[] javaOpts = { "-XX:ErgonomicsProfile=" + ergonomicsProfile, "-XX:+PrintFlagsFinal" }; + + DockerRunOptions opts = new DockerRunOptions(imageNameAndTag, "/jdk/bin/java", "-version", javaOpts); + opts.addDockerOpts("--rm"); + + DockerTestUtils.dockerRunJava(opts) + .shouldHaveExitValue(0) + .stdoutShouldMatch("ErgonomicsProfile.*" + expectedProfile); + } + + public static void testGCSelection(String profile, String expectedGC, int cpuCount, int memoryInMB) throws Exception { + String[] javaOpts = { "-XX:ErgonomicsProfile=" + profile, "-XX:+PrintFlagsFinal" }; + + DockerRunOptions opts = new DockerRunOptions(imageNameAndTag, "/jdk/bin/java", "-version", javaOpts); + opts.addDockerOpts("--rm", "--cpus", String.valueOf(cpuCount), "--memory", memoryInMB + "m"); + + OutputAnalyzer output = DockerTestUtils.dockerRunJava(opts); + output.shouldHaveExitValue(0); + + // Check GC + output.stdoutShouldMatch(expectedGC + ".*true"); + } + + private static void testMaxRAMPercentageSelection(String profile, String expectedMaxRAMPercentage, int physMem) + throws Exception { + String[] javaOpts = { "-XX:ErgonomicsProfile=" + profile, "-XX:+PrintFlagsFinal" }; + + DockerRunOptions opts = new DockerRunOptions(imageNameAndTag, "/jdk/bin/java", "-version", javaOpts); + opts.addDockerOpts("--rm", "--memory", physMem + "m"); + + // Run JVM with the given arguments + OutputAnalyzer output = DockerTestUtils.dockerRunJava(opts); + output.shouldHaveExitValue(0); + + // Check that MaxRAMPercentage is set to the expected value + output.shouldHaveExitValue(0); + output.stdoutShouldMatch("MaxRAMPercentage.*" + expectedMaxRAMPercentage); + } + +}