128 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			128 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Java
		
	
	
	
| /*
 | |
|  * Copyright (C) 2013 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.lang.Runtime;
 | |
| import java.lang.ref.ReferenceQueue;
 | |
| import java.lang.ref.PhantomReference;
 | |
| import dalvik.system.VMRuntime;
 | |
| 
 | |
| public class Main {
 | |
|     static Object deadlockLock = new Object();
 | |
|     static VMRuntime runtime = VMRuntime.getRuntime();
 | |
|     static volatile boolean aboutToDeadlock = false;
 | |
|     static final long MAX_EXPECTED_GC_DURATION_MS = 2000;
 | |
| 
 | |
|     // Save ref as a static field to ensure it doesn't get GC'd before the
 | |
|     // referent is enqueued.
 | |
|     static PhantomReference ref = null;
 | |
| 
 | |
|     static class DeadlockingFinalizer {
 | |
|         protected void finalize() throws Exception {
 | |
|             aboutToDeadlock = true;
 | |
|             synchronized (deadlockLock) { }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private static void allocateDeadlockingFinalizer() {
 | |
|         new DeadlockingFinalizer();
 | |
|     }
 | |
| 
 | |
|     public static PhantomReference allocPhantom(ReferenceQueue<Object> queue) {
 | |
|         return new PhantomReference(new Object(), queue);
 | |
|     }
 | |
| 
 | |
|     // Test that calling registerNativeAllocation triggers a GC eventually
 | |
|     // after a substantial number of registered native bytes.
 | |
|     private static void checkRegisterNativeAllocation() throws Exception {
 | |
|         long maxMem = Runtime.getRuntime().maxMemory();
 | |
|         int size = (int)(maxMem / 32);
 | |
|         int allocationCount = 256;
 | |
| 
 | |
|         ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
 | |
|         ref = allocPhantom(queue);
 | |
|         long total = 0;
 | |
|         for (int i = 0; !ref.isEnqueued() && i < allocationCount; ++i) {
 | |
|             runtime.registerNativeAllocation(size);
 | |
|             total += size;
 | |
| 
 | |
|             // Sleep a little bit to ensure not all of the calls to
 | |
|             // registerNativeAllocation complete while GC is in the process of
 | |
|             // running.
 | |
|             Thread.sleep(MAX_EXPECTED_GC_DURATION_MS / allocationCount);
 | |
|         }
 | |
| 
 | |
|         // Wait up to MAX_EXPECTED_GC_DURATION_MS to give GC a chance to finish
 | |
|         // running. If the reference isn't enqueued after that, then it is
 | |
|         // pretty unlikely (though technically still possible) that GC was
 | |
|         // triggered as intended.
 | |
|         if (queue.remove(MAX_EXPECTED_GC_DURATION_MS) == null) {
 | |
|             throw new RuntimeException("GC failed to complete");
 | |
|         }
 | |
| 
 | |
|         while (total > 0) {
 | |
|             runtime.registerNativeFree(size);
 | |
|             total -= size;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Call registerNativeAllocation repeatedly at a high rate to trigger the case of blocking
 | |
|     // registerNativeAllocation. Stop before we risk exhausting the finalizer timeout.
 | |
|     private static void triggerBlockingRegisterNativeAllocation() throws Exception {
 | |
|         final long startTime = System.currentTimeMillis();
 | |
|         final long finalizerTimeoutMs = VMRuntime.getRuntime().getFinalizerTimeoutMs();
 | |
|         final long quittingTime = startTime + finalizerTimeoutMs - MAX_EXPECTED_GC_DURATION_MS;
 | |
|         long maxMem = Runtime.getRuntime().maxMemory();
 | |
|         int size = (int)(maxMem / 5);
 | |
|         int allocationCount = 10;
 | |
| 
 | |
|         long total = 0;
 | |
|         for (int i = 0; i < allocationCount && System.currentTimeMillis() < quittingTime; ++i) {
 | |
|             runtime.registerNativeAllocation(size);
 | |
|             total += size;
 | |
|         }
 | |
| 
 | |
|         while (total > 0) {
 | |
|             runtime.registerNativeFree(size);
 | |
|             total -= size;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public static void main(String[] args) throws Exception {
 | |
|         // Test that registerNativeAllocation triggers GC.
 | |
|         // Run this a few times in a loop to reduce the chances that the test
 | |
|         // is flaky and make sure registerNativeAllocation continues to work
 | |
|         // after the first GC is triggered.
 | |
|         for (int i = 0; i < 20; ++i) {
 | |
|             checkRegisterNativeAllocation();
 | |
|         }
 | |
| 
 | |
|         // Test that we don't get a deadlock if we call
 | |
|         // registerNativeAllocation with a blocked finalizer.
 | |
|         synchronized (deadlockLock) {
 | |
|             allocateDeadlockingFinalizer();
 | |
|             while (!aboutToDeadlock) {
 | |
|                 Runtime.getRuntime().gc();
 | |
|             }
 | |
| 
 | |
|             // Do more allocations now that the finalizer thread is deadlocked so that we force
 | |
|             // finalization and timeout.
 | |
|             triggerBlockingRegisterNativeAllocation();
 | |
|         }
 | |
|         System.out.println("Test complete");
 | |
|     }
 | |
| }
 | |
| 
 |