Skip to content

Instantly share code, notes, and snippets.

@dimo414
Created March 26, 2013 04:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dimo414/5243162 to your computer and use it in GitHub Desktop.
Save dimo414/5243162 to your computer and use it in GitHub Desktop.
GC Churn Java application, intended to cause high amounts of garbage collection for benchmarking
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Runnable which causes a heavy yet stable amount of GC; useful for
* benchmarking applications under load.
*
* Intended to do the following:
* * Spend a decent amount (say, 20-50%) of time in GC
* * Do an approximately consistent amount of work over time,
* and create a similarly consistent amount of work for the GC
* * Avoid flooding the heap and triggering a Java heap space error
* * Avoid overloading the GC and triggering a GC overhead limit exceeded error
*/
public class GCChurn implements Runnable {
private static final int DUP_RATE = 75;
private final int churn;
private final int size;
private final int runFor;
private StringGenerator sg;
/**
* Constructs a GCChurn object with reasonable defaults
* @param runFor seconds to run for, 0 to run forever
*/
public GCChurn(int runFor) {
this(25, 500, runFor);
}
/**
* @param churn percent of data to churn each iteration (try 25%)
* @param size target data size (try 500)
* @param runFor seconds to run for, 0 to run forever
*/
public GCChurn(int churn, int size, int runFor) {
if(churn < 0 || churn > 100)
throw new IllegalArgumentException("Churn expects a valid percent, 0-100, was "+churn);
if(size < 0)
throw new IllegalArgumentException("Size cannot be negative, was "+size);
if(runFor < 0)
throw new IllegalArgumentException("RunFor cannot be negative, was "+runFor);
this.churn = churn;
this.size = size;
this.runFor = runFor;
sg = new StringGenerator(DUP_RATE,size*10);
}
@Override
/**
* Loops over a map of lists, adding and removing elements rapidly
* in order to cause GC, for runFor seconds, or until the thread is
* terminated.
*/
public void run() {
HashMap<String,ArrayList<String>> map = new HashMap<>();
long stop = System.currentTimeMillis() + 1000l * runFor;
while(runFor == 0 || System.currentTimeMillis() < stop) {
churn(map);
}
}
/**
* Three steps to churn the garbage collector:
* 1. Remove churn% of keys from the map
* 2. Remove churn% of strings from the lists in the map
* 3. Fill lists back up to size
* 4. Fill map back up to size
* @param map
*/
protected void churn(Map<String,ArrayList<String>> map) {
removeKeys(map);
churnValues(map);
addKeys(map);
}
/**
* Calculates the number of items to churn over, given a size
*/
private int churnSize(int size) {
return (int)Math.round(size * churn/100.0);
}
/**
* Selects churn% random keys from the map and removes them
*/
private void removeKeys(Map<String,ArrayList<String>> map) {
List<String> keys = new ArrayList<>(map.keySet());
Collections.shuffle(keys);
keys = keys.subList(0, churnSize(keys.size()));
for(String key : keys) {
map.remove(key);
}
}
/**
* Removes churn% items from the front of each list (to force an array copy)
* Then adds new strings to the front of the list until list is large enough
*/
private void churnValues(Map<String,ArrayList<String>> map) {
for(String key : map.keySet()) {
ArrayList<String> ls = map.get(key);
for(int i = 0; i < churnSize(ls.size()); i++) {
ls.remove(0);
}
while(ls.size() < size) {
ls.add(0, sg.next());
}
}
}
/**
* Adds new lists to the map using random keys until the map is large enough
* high likelyhood that the keys used have been seen before and will
* override existing values in the map, causing more GC
* @param map
*/
private void addKeys(Map<String,ArrayList<String>> map) {
while(map.size() < size) {
map.put(sg.next(), new ArrayList<String>());
}
}
public static void main(String[] args) {
// Runs a churn for 60 seconds
new GCChurn(25, 1000, 60).run();
}
}
import java.util.ArrayList;
import java.util.Random;
/** Generates random strings with a percentage likelyhood the string has been seen before */
public class StringGenerator {
private static final String chars;
static {
StringBuilder charBuilder = new StringBuilder();
for(char c = 'a'; c <= 'z'; c++) {
charBuilder.append(c);
}
for(char c = 'A'; c <= 'Z'; c++) {
charBuilder.append(c);
}
chars = charBuilder.toString();
}
private Random rnd = new Random();
private ArrayList<String> dupLs;
private int dupRate;
/**
* Constructs a new StringGenerator
* @param dups Percent (0-100) of strings which should be duplicates
* @param dupSize number of unique strings to use as duplicate values
*/
public StringGenerator(int dups, int dupSize) {
dupRate = dups;
dupLs = new ArrayList<String>(dupSize);
for(int i = 0; i < dupSize; i++) {
dupLs.add(genRandString());
}
}
/** Returns a random string with a percentage likelyhood of having been seen before */
public String next() {
if(rnd.nextInt(100) < dupRate) {
return dupLs.get(rnd.nextInt(dupLs.size()));
}
return genRandString();
}
private String genRandString() {
// 5 char minimum ensures reasonable likelihood of unique string
int strLen = rnd.nextInt(11)+5;
StringBuilder sb = new StringBuilder();
for(int i = 0; i < strLen; i++) {
sb.append(chars.charAt(rnd.nextInt(chars.length())));
}
return new String(sb.toString());
}
}
@dimo414
Copy link
Author

dimo414 commented Jun 25, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment