Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9240f16d authored by Brad Fitzpatrick's avatar Brad Fitzpatrick Committed by Android (Google) Code Review
Browse files

Merge "StrictMode: class instance limits (track object "leaks")" into honeycomb

parents b098f735 bfbe5771
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -146705,6 +146705,17 @@
 visibility="public"
>
</method>
<method name="detectActivityLeaks"
 return="android.os.StrictMode.VmPolicy.Builder"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
</method>
<method name="detectAll"
 return="android.os.StrictMode.VmPolicy.Builder"
 abstract="false"
+3 −0
Original line number Diff line number Diff line
@@ -4433,6 +4433,9 @@ public class Activity extends ContextThemeWrapper
            mStopped = true;
        }
        mResumed = false;

        // Check for Activity leaks, if enabled.
        StrictMode.conditionallyCheckInstanceCounts();
    }

    final void performDestroy() {
+131 −10
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import com.android.internal.os.RuntimeInit;

import dalvik.system.BlockGuard;
import dalvik.system.CloseGuard;
import dalvik.system.VMDebug;

import java.io.PrintWriter;
import java.io.StringWriter;
@@ -181,6 +182,15 @@ public final class StrictMode {
     */
    public static final int DETECT_VM_ACTIVITY_LEAKS = 0x800;  // for VmPolicy

    /**
     * @hide
     */
    private static final int DETECT_VM_INSTANCE_LEAKS = 0x1000;  // for VmPolicy

    private static final int ALL_VM_DETECT_BITS =
            DETECT_VM_CURSOR_LEAKS | DETECT_VM_CLOSABLE_LEAKS |
            DETECT_VM_ACTIVITY_LEAKS | DETECT_VM_INSTANCE_LEAKS;

    /**
     * @hide
     */
@@ -573,11 +583,15 @@ public final class StrictMode {
                } else if (mClassInstanceLimit == null) {
                    mClassInstanceLimit = new HashMap<Class, Integer>();
                }
                mMask |= DETECT_VM_INSTANCE_LEAKS;
                mClassInstanceLimit.put(klass, instanceLimit);
                return this;
            }

            private Builder detectActivityLeaks() {
            /**
             * Detect leaks of {@link android.app.Activity} subclasses.
             */
            public Builder detectActivityLeaks() {
                return enable(DETECT_VM_ACTIVITY_LEAKS);
            }

@@ -585,8 +599,8 @@ public final class StrictMode {
             * Detect everything that's potentially suspect.
             *
             * <p>In the Honeycomb release this includes leaks of
             * SQLite cursors and other closable objects but will
             * likely expand in future releases.
             * SQLite cursors, Activities, and other closable objects
             * but will likely expand in future releases.
             */
            public Builder detectAll() {
                return enable(DETECT_VM_ACTIVITY_LEAKS |
@@ -1346,6 +1360,41 @@ public final class StrictMode {
        gatheredViolations.set(null);
    }

    /**
     * @hide
     */
    public static void conditionallyCheckInstanceCounts() {
        VmPolicy policy = getVmPolicy();
        if (policy.classInstanceLimit.size() == 0) {
            return;
        }
        Runtime.getRuntime().gc();
        // Note: classInstanceLimit is immutable, so this is lock-free
        for (Class klass : policy.classInstanceLimit.keySet()) {
            int limit = policy.classInstanceLimit.get(klass);
            long instances = VMDebug.countInstancesOfClass(klass, false);
            if (instances <= limit) {
                continue;
            }
            Throwable tr = new InstanceCountViolation(klass, instances, limit);
            onVmPolicyViolation(tr.getMessage(), tr);
        }
    }

    private static long sLastInstanceCountCheckMillis = 0;
    private static boolean sIsIdlerRegistered = false;  // guarded by sProcessIdleHandler
    private static final MessageQueue.IdleHandler sProcessIdleHandler =
            new MessageQueue.IdleHandler() {
                public boolean queueIdle() {
                    long now = SystemClock.uptimeMillis();
                    if (now - sLastInstanceCountCheckMillis > 30 * 1000) {
                        sLastInstanceCountCheckMillis = now;
                        conditionallyCheckInstanceCounts();
                    }
                    return true;
                }
            };

    /**
     * Sets the policy for what actions in the VM process (on any
     * thread) should be detected, as well as the penalty if such
@@ -1357,6 +1406,19 @@ public final class StrictMode {
        sVmPolicy = policy;
        sVmPolicyMask = policy.mask;
        setCloseGuardEnabled(vmClosableObjectLeaksEnabled());

        Looper looper = Looper.getMainLooper();
        if (looper != null) {
            MessageQueue mq = looper.mQueue;
            synchronized (sProcessIdleHandler) {
                if (policy.classInstanceLimit.size() == 0) {
                    mq.removeIdleHandler(sProcessIdleHandler);
                } else if (!sIsIdlerRegistered) {
                    mq.addIdleHandler(sProcessIdleHandler);
                    sIsIdlerRegistered = true;
                }
            }
        }
    }

    /**
@@ -1406,19 +1468,39 @@ public final class StrictMode {
        onVmPolicyViolation(message, originStack);
    }

    // Map from VM violation fingerprint to uptime millis.
    private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>();

    /**
     * @hide
     */
    public static void onVmPolicyViolation(String message, Throwable originStack) {
        if ((sVmPolicyMask & PENALTY_LOG) != 0) {
            Log.e(TAG, message, originStack);
        final boolean penaltyDropbox = (sVmPolicyMask & PENALTY_DROPBOX) != 0;
        final boolean penaltyDeath = (sVmPolicyMask & PENALTY_DEATH) != 0;
        final boolean penaltyLog = (sVmPolicyMask & PENALTY_LOG) != 0;
        final ViolationInfo info = new ViolationInfo(originStack, sVmPolicyMask);

        final Integer fingerprint = info.hashCode();
        final long now = SystemClock.uptimeMillis();
        long lastViolationTime = 0;
        long timeSinceLastViolationMillis = Long.MAX_VALUE;
        synchronized (sLastVmViolationTime) {
            if (sLastVmViolationTime.containsKey(fingerprint)) {
                lastViolationTime = sLastVmViolationTime.get(fingerprint);
                timeSinceLastViolationMillis = now - lastViolationTime;
            }
            if (timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
                sLastVmViolationTime.put(fingerprint, now);
            }
        }

        boolean penaltyDropbox = (sVmPolicyMask & PENALTY_DROPBOX) != 0;
        boolean penaltyDeath = (sVmPolicyMask & PENALTY_DEATH) != 0;
        Log.d(TAG, "Time since last vm violation: " + timeSinceLastViolationMillis);

        if (penaltyLog && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
            Log.e(TAG, message, originStack);
        }

        int violationMaskSubset = PENALTY_DROPBOX | DETECT_VM_CURSOR_LEAKS;
        ViolationInfo info = new ViolationInfo(originStack, sVmPolicyMask);
        int violationMaskSubset = PENALTY_DROPBOX | (ALL_VM_DETECT_BITS & sVmPolicyMask);

        if (penaltyDropbox && !penaltyDeath) {
            // Common case for userdebug/eng builds.  If no death and
@@ -1428,7 +1510,7 @@ public final class StrictMode {
            return;
        }

        if (penaltyDropbox) {
        if (penaltyDropbox && lastViolationTime == 0) {
            // The violationMask, passed to ActivityManager, is a
            // subset of the original StrictMode policy bitmask, with
            // only the bit violated and penalty bits to be executed
@@ -1785,6 +1867,12 @@ public final class StrictMode {
         */
        public String broadcastIntentAction;

        /**
         * If this is a instance count violation, the number of instances in memory,
         * else -1.
         */
        public long numInstances = -1;

        /**
         * Create an uninitialized instance of ViolationInfo
         */
@@ -1806,6 +1894,9 @@ public final class StrictMode {
                broadcastIntentAction = broadcastIntent.getAction();
            }
            ThreadSpanState state = sThisThreadSpanState.get();
            if (tr instanceof InstanceCountViolation) {
                this.numInstances = ((InstanceCountViolation) tr).mInstances;
            }
            synchronized (state) {
                int spanActiveCount = state.mActiveSize;
                if (spanActiveCount > MAX_SPAN_TAGS) {
@@ -1867,6 +1958,7 @@ public final class StrictMode {
            violationNumThisLoop = in.readInt();
            numAnimationsRunning = in.readInt();
            violationUptimeMillis = in.readLong();
            numInstances = in.readLong();
            broadcastIntentAction = in.readString();
            tags = in.readStringArray();
        }
@@ -1881,6 +1973,7 @@ public final class StrictMode {
            dest.writeInt(violationNumThisLoop);
            dest.writeInt(numAnimationsRunning);
            dest.writeLong(violationUptimeMillis);
            dest.writeLong(numInstances);
            dest.writeString(broadcastIntentAction);
            dest.writeStringArray(tags);
        }
@@ -1895,6 +1988,9 @@ public final class StrictMode {
            if (durationMillis != -1) {
                pw.println(prefix + "durationMillis: " + durationMillis);
            }
            if (numInstances != -1) {
                pw.println(prefix + "numInstances: " + numInstances);
            }
            if (violationNumThisLoop != 0) {
                pw.println(prefix + "violationNumThisLoop: " + violationNumThisLoop);
            }
@@ -1914,4 +2010,29 @@ public final class StrictMode {
        }

    }

    // Dummy throwable, for now, since we don't know when or where the
    // leaked instances came from.  We might in the future, but for
    // now we suppress the stack trace because it's useless and/or
    // misleading.
    private static class InstanceCountViolation extends Throwable {
        final Class mClass;
        final long mInstances;
        final int mLimit;

        private static final StackTraceElement[] FAKE_STACK = new StackTraceElement[1];
        static {
            FAKE_STACK[0] = new StackTraceElement("android.os.StrictMode", "setClassInstanceLimit",
                                                  "StrictMode.java", 1);
        }

        public InstanceCountViolation(Class klass, long instances, int limit) {
            // Note: now including instances here, otherwise signatures would all be different.
            super(klass.toString() + "; limit=" + limit);
            setStackTrace(FAKE_STACK);
            mClass = klass;
            mInstances = instances;
            mLimit = limit;
        }
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -6817,6 +6817,9 @@ public final class ActivityManagerService extends ActivityManagerNative
            if (info.durationMillis != -1) {
                sb.append("Duration-Millis: ").append(info.durationMillis).append("\n");
            }
            if (info.numInstances != -1) {
                sb.append("Instance-Count: ").append(info.numInstances).append("\n");
            }
            if (info.tags != null) {
                for (String tag : info.tags) {
                    sb.append("Span-Tag: ").append(tag).append("\n");