diff --git a/src/utils/CodeCacheFragmenter/Makefile b/src/utils/CodeCacheFragmenter/Makefile new file mode 100644 index 0000000000000..8c82b042536da --- /dev/null +++ b/src/utils/CodeCacheFragmenter/Makefile @@ -0,0 +1,64 @@ +# +# Copyright Amazon.com Inc. 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. +# + +PKGLIST = jdk.tools.codecache + +# JDK source path (for WhiteBox) +JDK_SOURCE_PATH := $(shell cd ../../.. && pwd) + +WHITEBOX_DIR := $(JDK_SOURCE_PATH)/test/lib/jdk/test/whitebox + +SRC_DIR = src +BUILD_DIR = build +OUTPUT_DIR = $(BUILD_DIR)/classes + +FILELIST = \ +$(SRC_DIR)/main/java/jdk/tools/codecache/CodeCacheFragAgent.java \ +$(WHITEBOX_DIR)/WhiteBox.java \ +$(WHITEBOX_DIR)/parser/DiagnosticCommand.java \ +$(WHITEBOX_DIR)/code/*.java + +# Boot JDK +ifneq "x$(ALT_BOOTDIR)" "x" + BOOTDIR := $(ALT_BOOTDIR) +endif + +ifeq "x$(BOOTDIR)" "x" + JDK_HOME := $(shell dirname $(shell which java))/.. +else + JDK_HOME := $(BOOTDIR) +endif + +JAVAC = $(JDK_HOME)/bin/javac +JAR = $(JDK_HOME)/bin/jar + +all: codecachefragmenter.jar + +codecachefragmenter.jar: manifest.mf + @mkdir -p $(OUTPUT_DIR) + $(JAVAC) -sourcepath $(SRC_DIR) -d $(OUTPUT_DIR) $(FILELIST) + $(JAR) cvfm $@ manifest.mf -C $(OUTPUT_DIR) jdk + +clean: + rm -rf codecachefragmenter.jar + rm -rf $(BUILD_DIR) diff --git a/src/utils/CodeCacheFragmenter/README.md b/src/utils/CodeCacheFragmenter/README.md new file mode 100644 index 0000000000000..3e6c55c255bb3 --- /dev/null +++ b/src/utils/CodeCacheFragmenter/README.md @@ -0,0 +1,54 @@ +# CodeCacheFragmenter + +This is a simple Java agent for fragmenting the HotSpot code cache. Its main purpose is to create and randomly free dummy code blobs to reach a specified fill percentage. It requires a JDK source tree and a boot JDK to build. Simply running `make` will produce the agent jar. + +It produces `codecachefragmenter.jar`, which can be used as a Java agent like this: + +```bash +java -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -javaagent:codecachefragmenter.jar -Xbootclasspath/a:codecachefragmenter.jar YourMainClass +``` + +You can configure the agent using agent arguments: + +```bash +java \ + -XX:+UnlockDiagnosticVMOptions \ + -XX:+WhiteBoxAPI \ + -javaagent:codecachefragmenter.jar=MinBlobSize=500,MaxBlobSize=10000,AvgBlobSize=2000,DivBlobSize=500,RequiredStableGcRounds=3,FillPercentage=50.0,RandomSeed=12345 \ + -Xbootclasspath/a:codecachefragmenter.jar \ + YourMainClass +``` + +Key parameters: + +- **MinBlobSize**: minimum size of dummy code blobs (default: 500) +- **MaxBlobSize**: maximum size of dummy code blobs (default: 10000) +- **AvgBlobSize**: average size of blobs (default: 2000) +- **DivBlobSize**: standard deviation for blob sizes (default: 500) +- **RequiredStableGcRounds**: number of stable GC rounds before filling (default: 3) +- **FillPercentage**: target code cache fill percentage (0–100, default: 50.0) +- **RandomSeed**: seed for random generation (default: current time) + +To build the agent: + +```bash +make all +``` + +To clean build artifacts: + +```bash +make clean +``` + +You can override paths using environment variables: + +```bash +# JDK source tree +make ALT_JDK_SOURCE_PATH=/path/to/jdk/source all + +# Boot JDK +make ALT_BOOTDIR=/path/to/jdk all +``` + +This tool is intended for testing and experimentation with HotSpot code cache fragmentation. diff --git a/src/utils/CodeCacheFragmenter/manifest.mf b/src/utils/CodeCacheFragmenter/manifest.mf new file mode 100644 index 0000000000000..aeb2bce88839a --- /dev/null +++ b/src/utils/CodeCacheFragmenter/manifest.mf @@ -0,0 +1 @@ +Premain-Class: jdk.tools.codecache.CodeCacheFragAgent diff --git a/src/utils/CodeCacheFragmenter/src/main/java/jdk/tools/codecache/CodeCacheFragAgent.java b/src/utils/CodeCacheFragmenter/src/main/java/jdk/tools/codecache/CodeCacheFragAgent.java new file mode 100644 index 0000000000000..196199aa4573b --- /dev/null +++ b/src/utils/CodeCacheFragmenter/src/main/java/jdk/tools/codecache/CodeCacheFragAgent.java @@ -0,0 +1,202 @@ +package jdk.tools.codecache; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Random; +import java.util.random.RandomGenerator; +import jdk.test.whitebox.WhiteBox; +import jdk.test.whitebox.code.BlobType; +import jdk.test.whitebox.code.CodeBlob; + +public class CodeCacheFragAgent { + + // Configurable variables (can be overridden via agent arguments) + private static int minBlobSize = 500; + private static int maxBlobSize = 10000; + private static int avgBlobSize = 2000; + private static int divBlobSize = 500; + private static int requiredStableGcRounds = 3; + private static double fillPercent = 50.0; + + private static final WhiteBox WHITEBOX = WhiteBox.getWhiteBox(); + private static long seed = System.currentTimeMillis(); + + // Parse agent arguments and update configurable variables + private static void parseArguments(String args) { + if (args == null || args.trim().isEmpty()) { + return; // Use default values + } + + String[] pairs = args.split(","); + for (String pair : pairs) { + String[] keyValue = pair.split("=", 2); + if (keyValue.length != 2) { + System.out.println("Invalid argument format: " + pair + ". Expected key=value"); + continue; + } + + String key = keyValue[0].trim(); + String value = keyValue[1].trim(); + + try { + switch (key) { + case "MinBlobSize": + minBlobSize = Integer.parseInt(value); + break; + case "MaxBlobSize": + maxBlobSize = Integer.parseInt(value); + break; + case "AvgBlobSize": + avgBlobSize = Integer.parseInt(value); + break; + case "DivBlobSize": + divBlobSize = Integer.parseInt(value); + break; + case "RequiredStableGcRounds": + requiredStableGcRounds = Integer.parseInt(value); + break; + case "FillPercentage": + fillPercent = Double.parseDouble(value); + break; + case "RandomSeed": + seed = Long.parseLong(value); + break; + default: + System.out.println("Unknown parameter: " + key); + } + } catch (NumberFormatException e) { + System.out.println("Invalid value for " + key + ": " + value + ". Must be a number."); + } + } + } + + public static void premain(String args) { + // Parse agent arguments to configure variables + parseArguments(args); + + // Validate parameters + if (fillPercent < 0.0 || fillPercent > 100.0) { + System.out.println("FillPercentage must be between 0 and 100: " + fillPercent); + return; + } + + if (minBlobSize <= 0 || maxBlobSize <= 0 || avgBlobSize <= 0 || divBlobSize <= 0) { + System.out.println("Blob size parameters must be positive values"); + return; + } + + if (minBlobSize > maxBlobSize) { + System.out.println("MinBlobSize (" + minBlobSize + ") cannot be greater than MaxBlobSize (" + maxBlobSize + ")"); + return; + } + + if (requiredStableGcRounds <= 0) { + System.out.println("RequiredStableGcRounds must be positive: " + requiredStableGcRounds); + return; + } + + // Ensure no nmethods get added to CodeCache + WHITEBOX.lockCompilation(); + + // Deoptimized all nmethods + WHITEBOX.deoptimizeAll(); + + // Trigger GC until the number of non profiled blobs is stable + gcUntilNonProfiledStable(); + + // Check total size of non profiled heap + long nonProfiledSize = BlobType.MethodNonProfiled.getSize(); + if (nonProfiledSize == 0) { + return; + } + + // Fill up NonProfiled heap with randomly sized dummy blobs + List blobs = new ArrayList(); + + RandomGenerator blobGen = new Random(seed); + while (true) { + long addr = generateDummyBlob(blobGen); + + if (addr == -1) { + break; + } + + blobs.add(addr); + } + + // Create list of indices + List indices = new ArrayList<>(); + for (int i = 0; i < blobs.size(); i++) { + indices.add(i); + } + + // Randomize the indicies. Use sort with a random deterministic value + // This keeps relative ordering regardless of the number of blobs generated + indices.sort(Comparator.comparingLong(i -> new java.util.SplittableRandom(i + seed).nextLong())); + + // Free blobs until we hit the desired fragmentation amount + long currentFilled = nonProfiledSize; + for (Integer index : indices) { + if (100.0 * currentFilled / nonProfiledSize <= fillPercent) { + break; + } + + long addr = blobs.get(index); + currentFilled -= CodeBlob.getCodeBlob(addr).size; + WHITEBOX.freeCodeBlob(addr); + } + + // Unlock compilation + WHITEBOX.unlockCompilation(); + } + + // Generate a randomly sized code cache blob + // Returns the address if successfully created, otherwise returns -1 + public static long generateDummyBlob(RandomGenerator blobGen) { + int size = -1; + + while (size < minBlobSize || size > maxBlobSize) { + size = (int) blobGen.nextGaussian(avgBlobSize, divBlobSize); + } + + long addr = WHITEBOX.allocateCodeBlob(size, BlobType.MethodNonProfiled.id); + if (addr == 0) { + return -1; + } + + BlobType actualType = CodeBlob.getCodeBlob(addr).code_blob_type; + if (actualType.id != BlobType.MethodNonProfiled.id) { + WHITEBOX.freeCodeBlob(addr); + return -1; + } + + return addr; + } + + // Returns the number of live CodeBlobs currently present in the + // MethodNonProfiled code heap + private static int countNonProfiledCodeBlobs() { + Object[] entries = WHITEBOX.getCodeHeapEntries(BlobType.MethodNonProfiled.id); + return entries == null ? 0 : entries.length; + } + + // Run GCs until the MethodNonProfiled code heap blob count stabilizes + private static void gcUntilNonProfiledStable() { + int previousBlobs = Integer.MAX_VALUE; + int stableCount = 0; + + while (stableCount < requiredStableGcRounds) { + WHITEBOX.fullGC(); + int currentBlobs = countNonProfiledCodeBlobs(); + + if (currentBlobs < previousBlobs) { + stableCount = 0; + } else { + stableCount++; + } + + previousBlobs = currentBlobs; + } + } +}