Home | History | Annotate | Download | only in src-art
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 import java.lang.Runtime;
     18 import java.lang.ref.ReferenceQueue;
     19 import java.lang.ref.PhantomReference;
     20 import dalvik.system.VMRuntime;
     21 
     22 public class Main {
     23     static Object deadlockLock = new Object();
     24     static VMRuntime runtime = VMRuntime.getRuntime();
     25     static volatile boolean aboutToDeadlock = false;
     26     static final long MAX_EXPECTED_GC_DURATION_MS = 2000;
     27 
     28     // Save ref as a static field to ensure it doesn't get GC'd before the
     29     // referent is enqueued.
     30     static PhantomReference ref = null;
     31 
     32     static class DeadlockingFinalizer {
     33         protected void finalize() throws Exception {
     34             aboutToDeadlock = true;
     35             synchronized (deadlockLock) { }
     36         }
     37     }
     38 
     39     private static void allocateDeadlockingFinalizer() {
     40         new DeadlockingFinalizer();
     41     }
     42 
     43     public static PhantomReference allocPhantom(ReferenceQueue<Object> queue) {
     44         return new PhantomReference(new Object(), queue);
     45     }
     46 
     47     // Test that calling registerNativeAllocation triggers a GC eventually
     48     // after a substantial number of registered native bytes.
     49     private static void checkRegisterNativeAllocation() throws Exception {
     50         long maxMem = Runtime.getRuntime().maxMemory();
     51         int size = (int)(maxMem / 32);
     52         int allocationCount = 256;
     53 
     54         ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
     55         ref = allocPhantom(queue);
     56         long total = 0;
     57         for (int i = 0; !ref.isEnqueued() && i < allocationCount; ++i) {
     58             runtime.registerNativeAllocation(size);
     59             total += size;
     60 
     61             // Sleep a little bit to ensure not all of the calls to
     62             // registerNativeAllocation complete while GC is in the process of
     63             // running.
     64             Thread.sleep(MAX_EXPECTED_GC_DURATION_MS / allocationCount);
     65         }
     66 
     67         // Wait up to MAX_EXPECTED_GC_DURATION_MS to give GC a chance to finish
     68         // running. If the reference isn't enqueued after that, then it is
     69         // pretty unlikely (though technically still possible) that GC was
     70         // triggered as intended.
     71         if (queue.remove(MAX_EXPECTED_GC_DURATION_MS) == null) {
     72             throw new RuntimeException("GC failed to complete");
     73         }
     74 
     75         while (total > 0) {
     76             runtime.registerNativeFree(size);
     77             total -= size;
     78         }
     79     }
     80 
     81     // Call registerNativeAllocation repeatedly at a high rate to trigger the case of blocking
     82     // registerNativeAllocation. Stop before we risk exhausting the finalizer timeout.
     83     private static void triggerBlockingRegisterNativeAllocation() throws Exception {
     84         final long startTime = System.currentTimeMillis();
     85         final long finalizerTimeoutMs = VMRuntime.getRuntime().getFinalizerTimeoutMs();
     86         final long quittingTime = startTime + finalizerTimeoutMs - MAX_EXPECTED_GC_DURATION_MS;
     87         long maxMem = Runtime.getRuntime().maxMemory();
     88         int size = (int)(maxMem / 5);
     89         int allocationCount = 10;
     90 
     91         long total = 0;
     92         for (int i = 0; i < allocationCount && System.currentTimeMillis() < quittingTime; ++i) {
     93             runtime.registerNativeAllocation(size);
     94             total += size;
     95         }
     96 
     97         while (total > 0) {
     98             runtime.registerNativeFree(size);
     99             total -= size;
    100         }
    101     }
    102 
    103     public static void main(String[] args) throws Exception {
    104         // Test that registerNativeAllocation triggers GC.
    105         // Run this a few times in a loop to reduce the chances that the test
    106         // is flaky and make sure registerNativeAllocation continues to work
    107         // after the first GC is triggered.
    108         for (int i = 0; i < 20; ++i) {
    109             checkRegisterNativeAllocation();
    110         }
    111 
    112         // Test that we don't get a deadlock if we call
    113         // registerNativeAllocation with a blocked finalizer.
    114         synchronized (deadlockLock) {
    115             allocateDeadlockingFinalizer();
    116             while (!aboutToDeadlock) {
    117                 Runtime.getRuntime().gc();
    118             }
    119 
    120             // Do more allocations now that the finalizer thread is deadlocked so that we force
    121             // finalization and timeout.
    122             triggerBlockingRegisterNativeAllocation();
    123         }
    124         System.out.println("Test complete");
    125     }
    126 }
    127 
    128