/* * Copyright (C) 2021 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 dalvik.system.VMRuntime; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; public class Main { static final int HOW_MANY_HUGE = 120; // > 1GB to trigger blocking in default config. int allocated = 0; int deallocated = 0; static Object lock = new Object(); final static int MAX_TRIES = 10; WeakReference[] references = new WeakReference[HOW_MANY_HUGE]; class BufferHolder { private ByteBuffer buffer; BufferHolder() { ++allocated; buffer = getHugeNativeBuffer(); } protected void finalize() { synchronized(lock) { ++deallocated; } deleteHugeNativeBuffer(buffer); buffer = null; } } // Repeatedly inform the GC of native allocations. Return the time (in nsecs) this takes. private static long timeNotifications() { final VMRuntime vmr = VMRuntime.getRuntime(); final long startNanos = System.nanoTime(); // Iteration count must be >= Heap::kNotifyNativeInterval. for (int i = 0; i < 400; ++i) { vmr.notifyNativeAllocation(); } return System.nanoTime() - startNanos; } public static void main(String[] args) { System.loadLibrary(args[0]); System.out.println("Main Started"); for (int i = 1; i <= MAX_TRIES; ++i) { Runtime.getRuntime().gc(); if (new Main().tryToRun(i == MAX_TRIES)) { break; } if (i == MAX_TRIES / 2) { // Maybe some transient CPU load is causing issues here? try { Thread.sleep(3000); } catch (InterruptedException ignored) { System.out.println("Unexpected interrupt"); } } // Clean up and try again. Runtime.getRuntime().gc(); System.runFinalization(); } System.out.println("Main Finished"); } // Returns false on a failure that should be retried. boolean tryToRun(boolean lastChance) { final int startingGcNum = getGcNum(); timeNotifications(); // warm up. final long referenceTime1 = timeNotifications(); final long referenceTime2 = timeNotifications(); final long referenceTime3 = timeNotifications(); final long referenceTime = Math.min(referenceTime1, Math.min(referenceTime2, referenceTime3)); // Allocate a GB+ of native memory without informing the GC. for (int i = 0; i < HOW_MANY_HUGE; ++i) { new BufferHolder(); } if (startingGcNum != getGcNum()) { // Happens rarely, fail and retry. if (!lastChance) { return false; } System.out.println("Triggered early GC"); } // One of the notifications should block for GC to catch up. long actualTime = timeNotifications(); final long minBlockingTime = 2 * referenceTime + 2_000_000; if (startingGcNum == getGcNum()) { System.out.println("No gc completed"); } if (actualTime > 500_000_000) { System.out.println("Notifications ran too slowly; excessive blocking? msec = " + (actualTime / 1_000_000)); } else if (actualTime < minBlockingTime) { if (!lastChance) { // We sometimes see this, maybe because a GC is triggered by other means? // Try again before reporting. return false; } System.out.println("Notifications ran too quickly; no blocking GC? msec = " + (actualTime / 1_000_000) + " reference(msec) = " + (referenceTime / 1_000_000)); } // Let finalizers run. try { Thread.sleep(3000); } catch (InterruptedException e) { System.out.println("Unexpected interrupt"); } if (deallocated > allocated || deallocated < allocated - 5 /* slop for register references */) { System.out.println("Unexpected number of deallocated objects:"); System.out.println("Allocated = " + allocated + " deallocated = " + deallocated); } System.out.println("Succeeded"); return true; } private static native ByteBuffer getHugeNativeBuffer(); private static native void deleteHugeNativeBuffer(ByteBuffer buf); private static native int getGcNum(); }