Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/utils/CodeCacheFragmenter/Makefile
Original file line number Diff line number Diff line change
@@ -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)
54 changes: 54 additions & 0 deletions src/utils/CodeCacheFragmenter/README.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions src/utils/CodeCacheFragmenter/manifest.mf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Premain-Class: jdk.tools.codecache.CodeCacheFragAgent
Original file line number Diff line number Diff line change
@@ -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<Long> blobs = new ArrayList<Long>();

RandomGenerator blobGen = new Random(seed);
while (true) {
long addr = generateDummyBlob(blobGen);

if (addr == -1) {
break;
}

blobs.add(addr);
}

// Create list of indices
List<Integer> 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;
}
}
}