/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.CyclicBarrier;
public class Main implements Runnable {
// Timeout in minutes. Make it larger than the run-test timeout to get a native thread dump by
// ART on timeout when running on the host.
private final static long TIMEOUT_VALUE = 7;
private final static long MAX_SIZE = 1000; // Maximum size of array-list to allocate.
private final static int THREAD_COUNT = 16;
// Use a couple of different forms of synchronizing to test some of these...
private final static AtomicInteger counter = new AtomicInteger();
private final static Object gate = new Object();
private volatile static int waitCount = 0;
public static void main(String[] args) throws Exception {
Thread[] threads = new Thread[THREAD_COUNT];
// This barrier is used to synchronize the threads starting to allocate.
// Note: Even though a barrier is not allocation-free, this one is fine, as it will be used
// before filling the heap.
CyclicBarrier startBarrier = new CyclicBarrier(threads.length);
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Main(startBarrier));
threads[i].start();
}
// Wait for the threads to finish.
for (Thread thread : threads) {
thread.join();
}
// Allocate objects to definitely run GC before quitting.
allocateObjectsToRunGc();
new ArrayList<Object>(50);
}
private static void allocateObjectsToRunGc() {
ArrayList<Object> l = new ArrayList<Object>();
try {
for (int i = 0; i < 100000; i++) {
l.add(new ArrayList<Object>(i));
}
} catch (OutOfMemoryError oom) {
}
}
private Main(CyclicBarrier startBarrier) {
this.startBarrier = startBarrier;
}
private ArrayList<Object> store;
private CyclicBarrier startBarrier;
public void run() {
try {
work();
} catch (Throwable t) {
// Any exception or error getting here is bad.
try {
// May need allocations...
t.printStackTrace(System.err);
} catch (Throwable tInner) {
}
System.exit(1);
}
}
private void work() throws Exception {
// Any exceptions except an OOME in the allocation loop are bad and handed off to the
// caller which should abort the whole runtime.
ArrayList<Object> l = new ArrayList<Object>();
store = l; // Keep it alive.
// Wait for the start signal.
startBarrier.await(TIMEOUT_VALUE, java.util.concurrent.TimeUnit.MINUTES);
// Allocate.
try {
for (int i = 0; i < MAX_SIZE; i++) {
l.add(new ArrayList<Object>(i));
}
} catch (OutOfMemoryError oome) {
// Fine, we're done.
}
// Atomically increment the counter and check whether we were last.
int number = counter.incrementAndGet();
if (number < THREAD_COUNT) {
// Not last.
synchronized (gate) {
// Increment the wait counter.
waitCount++;
gate.wait(TIMEOUT_VALUE * 1000 * 60);
}
} else {
// Last. Wait until waitCount == THREAD_COUNT - 1.
for (int loops = 0; ; loops++) {
synchronized (gate) {
if (waitCount == THREAD_COUNT - 1) {
// OK, everyone's waiting. Notify and break out.
gate.notifyAll();
break;
} else if (loops > 40) {
// 1s wait, too many tries.
System.out.println("Waited too long for the last thread.");
System.exit(1);
}
}
// Wait a bit.
Thread.sleep(25);
}
}
store = null; // Allow GC to reclaim it.
}
}