// Run on host with: // javac ThreadTest.java && java ThreadStress && rm *.class // Through run-test: // test/run-test {run-test-args} 004-ThreadStress [Main {ThreadStress-args}] // (It is important to pass Main if you want to give parameters...) // // ThreadStress command line parameters: // -n X .............. number of threads // -d X .............. number of daemon threads // -o X .............. number of overall operations // -t X .............. number of operations per thread // -p X .............. number of permits granted by semaphore // --dumpmap ......... print the frequency map // --locks-only ...... select a pre-set frequency map with lock-related operations only // --allocs-only ..... select a pre-set frequency map with allocation-related operations only // -oom:X ............ frequency of OOM (double) // -sigquit:X ........ frequency of SigQuit (double) // -alloc:X .......... frequency of Alloc (double) // -largealloc:X ..... frequency of LargeAlloc (double) // -nonmovingalloc:X.. frequency of NonMovingAlloc (double) // -stacktrace:X ..... frequency of StackTrace (double) // -exit:X ........... frequency of Exit (double) // -sleep:X .......... frequency of Sleep (double) // -wait:X ........... frequency of Wait (double) // -timedwait:X ...... frequency of TimedWait (double) // -timedpark:X ...... frequency of TimedPark (double) // -syncandwork:X .... frequency of SyncAndWork (double) // -queuedwait:X ..... frequency of QueuedWait (double)
privatefinalstaticclass Alloc extends Operation { privatefinalstaticint ALLOC_SIZE = 1024; // Needs to be small enough to not be in LOS. privatefinalstaticint ALLOC_COUNT = 1024;
@Override publicboolean perform() { try {
List<byte[]> l = new ArrayList<byte[]>(); for (int i = 0; i < ALLOC_COUNT; i++) {
l.add(newbyte[ALLOC_SIZE]);
}
} catch (OutOfMemoryError e) {
} returntrue;
}
}
privatefinalstaticclass LargeAlloc extends Operation { privatefinalstaticint PAGE_SIZE = 4096; privatefinalstaticint PAGE_SIZE_MODIFIER = 10; // Needs to be large enough for LOS. privatefinalstaticint ALLOC_COUNT = 100;
@Override publicboolean perform() { try {
List<byte[]> l = new ArrayList<byte[]>(); for (int i = 0; i < ALLOC_COUNT; i++) {
l.add(newbyte[PAGE_SIZE_MODIFIER * PAGE_SIZE]);
}
} catch (OutOfMemoryError e) {
} returntrue;
}
}
privatefinalstaticclass NonMovingAlloc extends Operation { privatefinalstaticint ALLOC_SIZE = 1024; // Needs to be small enough to not be in LOS. privatefinalstaticint ALLOC_COUNT = 1024; privatefinalstatic VMRuntime runtime = VMRuntime.getRuntime();
@Override publicboolean perform() { try {
List<byte[]> l = new ArrayList<byte[]>(); for (int i = 0; i < ALLOC_COUNT; i++) {
l.add((byte[]) runtime.newNonMovableArray(byte.class, ALLOC_SIZE));
}
} catch (OutOfMemoryError e) {
} returntrue;
}
}
// An operation requiring the acquisition of a permit from a semaphore // for its execution. This operation has been added to exercise // java.util.concurrent.locks.AbstractQueuedSynchronizer, used in the // implementation of java.util.concurrent.Semaphore. We use the latter, // as the former is not supposed to be used directly (see b/63822989). privatefinalstaticclass QueuedWait extends Operation { privatefinalstaticint SLEEP_TIME = 100;
privatefinal Semaphore semaphore;
public QueuedWait(Semaphore semaphore) { this.semaphore = semaphore;
}
@Override publicboolean perform() { boolean permitAcquired = false; try {
semaphore.acquire();
permitAcquired = true; Thread.sleep(SLEEP_TIME);
} catch (OutOfMemoryError ignored) { // The call to semaphore.acquire() above may trigger an OOME, // despite the care taken doing some warm-up by forcing // ahead-of-time initialization of classes used by the Semaphore // class (see forceTransitiveClassInitialization below). // For instance, one of the code paths executes // AbstractQueuedSynchronizer.addWaiter, which allocates an // AbstractQueuedSynchronizer$Node (see b/67730573). // In that case, just ignore the OOME and continue.
} catch (InterruptedException ignored) {
} finally { if (permitAcquired) {
semaphore.release();
}
} returntrue;
}
}
if (dumpMap) {
System.out.println(frequencyMap);
}
try {
runTest(numberOfThreads, numberOfDaemons, operationsPerThread, lock, frequencyMap);
} catch (Throwable t) { // In this case, the output should not contain all the required // "Finishing worker" lines.
Main.printThrowable(t);
}
}
privatestatic Semaphore getSemaphore(int permits) { if (permits == -1) { // Default number of permits.
permits = 3;
}
// Force ahead-of-time initialization of classes used by Semaphore // code. Try to exercise all code paths likely to be taken during // the actual test later (including having a thread blocking on // the semaphore trying to acquire a permit), so that we increase // the chances to initialize all classes indirectly used by // QueuedWait (e.g. AbstractQueuedSynchronizer$Node). privatestaticvoid forceTransitiveClassInitialization(Semaphore semaphore, finalint permits) { // Ensure `semaphore` has the expected number of permits // before we start. assert semaphore.availablePermits() == permits;
// Let the main (current) thread acquire all permits from // `semaphore`. Then create an auxiliary thread acquiring a // permit from `semaphore`, blocking because none is // available. Have the main thread release one permit, thus // unblocking the second thread.
// Auxiliary thread. Thread auxThread = newThread("Aux") { publicvoid run() { try { // Try to acquire one permit, and block until // that permit is released by the main thread.
semaphore.acquire(); // When unblocked, release the acquired permit // immediately.
semaphore.release();
} catch (InterruptedException ignored) { thrownew RuntimeException("Test set up failed in auxiliary thread");
}
}
};
// Main thread. try { // Acquire all permits.
semaphore.acquire(permits); // Start the auxiliary thread and have it try to acquire a // permit.
auxThread.start(); // Synchronization: Wait until the auxiliary thread is // blocked trying to acquire a permit from `semaphore`. while (!semaphore.hasQueuedThreads()) { Thread.sleep(100);
} // Release one permit, thus unblocking `auxThread` and let // it acquire a permit.
semaphore.release(); // Synchronization: Wait for the auxiliary thread to die.
auxThread.join(); // Release remaining permits.
semaphore.release(permits - 1);
// Verify that all permits have been released. assert semaphore.availablePermits() == permits;
} catch (InterruptedException ignored) { thrownew RuntimeException("Test set up failed in main thread");
}
}
publicstaticvoid runTest(finalint numberOfThreads, finalint numberOfDaemons, finalint operationsPerThread, final Object lock,
Map<Operation, Double> frequencyMap) throws Exception { finalThread mainThread = Thread.currentThread(); final Barrier startBarrier = new Barrier(numberOfThreads + numberOfDaemons + 1);
// Each normal thread is going to do operationsPerThread // operations. Each daemon thread will loop over all // the operations and will not stop. // The distribution of operations is determined by // the frequencyMap values. We fill out an Operation[] // for each thread with the operations it is to perform. The // Operation[] is shuffled so that there is more random // interactions between the threads.
// Fill in the Operation[] array for each thread by laying // down references to operation according to their desired // frequency. // The first numberOfThreads elements are normal threads, the last // numberOfDaemons elements are daemon threads. final Main[] threadStresses = new Main[numberOfThreads + numberOfDaemons]; for (int t = 0; t < threadStresses.length; t++) {
Operation[] operations = new Operation[operationsPerThread]; int o = 0;
LOOP: while (true) { for (Operation op : frequencyMap.keySet()) { int freq = (int)(frequencyMap.get(op) * operationsPerThread); for (int f = 0; f < freq; f++) { if (o == operations.length) { break LOOP;
}
operations[o] = op;
o++;
}
}
} // Randomize the operation order
Collections.shuffle(Arrays.asList(operations));
threadStresses[t] = (t < numberOfThreads)
? new Main(lock, t, operations)
: new Daemon(lock, t, operations, mainThread, startBarrier);
}
// Enable to dump operation counts per thread to see that it is // commensurate with the frequencyMap. if (DEBUG) { for (int t = 0; t < threadStresses.length; t++) {
Operation[] operations = threadStresses[t].operations;
Map<Operation, Integer> distribution = new HashMap<Operation, Integer>(); for (Operation operation : operations) {
Integer ops = distribution.get(operation); if (ops == null) {
ops = 1;
} else {
ops++;
}
distribution.put(operation, ops);
}
System.out.println("Distribution for " + t); for (Operation op : frequencyMap.keySet()) {
System.out.println(op + " = " + distribution.get(op));
}
}
}
// Create the runners for each thread. The runner Thread // ensures that thread that exit due to operation Exit will be // restarted until they reach their desired // operationsPerThread. Thread[] runners = newThread[numberOfThreads]; for (int r = 0; r < runners.length; r++) { final Main ts = threadStresses[r];
runners[r] = newThread("Runner thread " + r) { final Main threadStress = ts; publicvoid run() { try { int id = threadStress.id; // No memory hungry task are running yet, so println() should succeed.
System.out.println("Starting worker for " + id); // Wait until all runners and daemons reach the starting point.
startBarrier.await(); // Run the stress tasks. while (threadStress.nextOperation < operationsPerThread) { try { Threadthread = newThread(ts, "Worker thread " + id); thread.start(); thread.join();
if (DEBUG) {
System.out.println( "Thread exited for " + id + " with " +
(operationsPerThread - threadStress.nextOperation) + " operations remaining.");
}
} catch (OutOfMemoryError e) { // Ignore OOME since we need to print "Finishing worker" // for the test to pass. This OOM can come from creating // the Thread or from the DEBUG output. // Note that the Thread creation may fail repeatedly, // preventing the runner from making any progress, // especially if the number of daemons is too high.
}
} // Print "Finishing worker" through JNI to avoid OOME.
Main.printString(Main.finishingWorkerMessage);
} catch (Throwable t) {
Main.printThrowable(t); // Interrupt the main thread, so that it can orderly shut down // instead of waiting indefinitely for some Barrier.
mainThread.interrupt();
}
}
};
}
// The notifier thread is a daemon just loops forever to wake // up threads in operations Wait and Park. if (lock != null) { Thread notifier = newThread("Notifier") { publicvoid run() { while (true) { synchronized (lock) {
lock.notifyAll();
} for (Thread runner : runners) { if (runner != null) {
LockSupport.unpark(runner);
}
}
}
}
};
notifier.setDaemon(true);
notifier.start();
}
// Create and start the daemon threads. for (int r = 0; r < numberOfDaemons; r++) {
Main daemon = threadStresses[numberOfThreads + r]; Thread t = newThread(daemon, "Daemon thread " + daemon.id);
t.setDaemon(true);
t.start();
}
for (int r = 0; r < runners.length; r++) {
runners[r].start();
} // Wait for all threads to reach the starting point.
startBarrier.await(); // Wait for runners to finish. for (int r = 0; r < runners.length; r++) {
runners[r].join();
}
}
publicvoid run() { try { if (DEBUG) {
System.out.println("Starting ThreadStress Daemon " + id);
}
startBarrier.await(); try { int i = 0; while (true) {
Operation operation = operations[i]; if (DEBUG) {
System.out.println("ThreadStress Daemon " + id
+ " operation " + i
+ " is " + operation);
} // Ignore the result of the performed operation, making // Exit.perform() essentially a no-op for daemon threads.
operation.perform();
i = (i + 1) % operations.length;
}
} catch (OutOfMemoryError e) { // Catch OutOfMemoryErrors since these can cause the test to fail it they print // the stack trace after "Finishing worker". Note that operations should catch // their own OOME, this guards only agains OOME in the DEBUG output.
} if (DEBUG) {
System.out.println("Finishing ThreadStress Daemon for " + id);
}
} catch (Throwable t) {
Main.printThrowable(t); // Interrupt the main thread, so that it can orderly shut down // instead of waiting indefinitely for some Barrier.
mainThread.interrupt();
}
}
finalThread mainThread; final Barrier startBarrier;
}
// Note: java.util.concurrent.CyclicBarrier.await() allocates memory and may throw OOM. // That is highly undesirable in this test, so we use our own simple barrier class. // The only memory allocation that can happen here is the lock inflation which uses // a native allocation. As such, it should succeed even if the Java heap is full. // If the native allocation surprisingly fails, the program shall abort(). privatestaticclass Barrier { public Barrier(int initialCount) {
count = initialCount;
}
publicsynchronizedvoid await() throws InterruptedException {
--count; if (count != 0) { do {
wait();
} while (count != 0); // Check for spurious wakeup.
} else {
notifyAll();
}
}
privateint count;
}
// Printing a String/Throwable through JNI requires only native memory and space // in the local reference table, so it should succeed even if the Java heap is full. privatestaticnativevoid printString(String s); privatestaticnativevoid printThrowable(Throwable t);
staticfinal String finishingWorkerMessage; staticfinal String errnoExceptionName; static { // We pre-allocate the strings in class initializer to avoid const-string // instructions in code using these strings later as they may throw OOME.
finishingWorkerMessage = "Finishing worker\n";
errnoExceptionName = "ErrnoException";
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.4 Sekunden
(vorverarbeitet am 2026-06-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.