/* * 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. } }