Loading packages/SystemUI/src/com/android/systemui/Dependency.java +3 −0 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.leak.LeakDetector; import java.io.FileDescriptor; import java.io.PrintWriter; Loading Loading @@ -187,6 +188,8 @@ public class Dependency extends SystemUI { mProviders.put(SecurityController.class.getName(), () -> new SecurityControllerImpl(mContext)); mProviders.put(LeakDetector.class.getName(), LeakDetector::create); mProviders.put(TunerService.class.getName(), () -> new TunerService(mContext)); Loading packages/SystemUI/src/com/android/systemui/util/leak/AbstractCollection.java 0 → 100644 +83 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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. */ package com.android.systemui.util.leak; import java.util.Collection; import java.util.Iterator; abstract class AbstractCollection<T> implements Collection<T> { @Override public abstract int size(); @Override public abstract boolean isEmpty(); @Override public boolean contains(Object o) { throw new UnsupportedOperationException(); } @Override public Iterator<T> iterator() { throw new UnsupportedOperationException(); } @Override public Object[] toArray() { throw new UnsupportedOperationException(); } @Override public <T1> T1[] toArray(T1[] t1s) { throw new UnsupportedOperationException(); } @Override public boolean add(T t) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection<?> collection) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends T> collection) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> collection) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> collection) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } } packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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. */ package com.android.systemui.util.leak; import android.os.Build; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.systemui.Dumpable; import java.io.FileDescriptor; import java.io.PrintWriter; import java.io.Writer; import java.util.Collection; /** * Detects leaks. */ public class LeakDetector implements Dumpable { private static final boolean ENABLED = Build.IS_DEBUGGABLE; private final TrackedCollections mTrackedCollections; private final TrackedGarbage mTrackedGarbage; private final TrackedObjects mTrackedObjects; @VisibleForTesting public LeakDetector(TrackedCollections trackedCollections, TrackedGarbage trackedGarbage, TrackedObjects trackedObjects) { mTrackedCollections = trackedCollections; mTrackedGarbage = trackedGarbage; mTrackedObjects = trackedObjects; } /** * Tracks an instance that has a high leak risk (i.e. has complex ownership and references * a large amount of memory). * * The LeakDetector will monitor and keep weak references to such instances, dump statistics * about them in a bugreport, and in the future dump the heap if their count starts growing * unreasonably. * * This should be called when the instance is first constructed. */ public <T> void trackInstance(T object) { if (mTrackedObjects != null) { mTrackedObjects.track(object); } } /** * Tracks a collection that is at risk of leaking large objects, e.g. a collection of * dynamically registered listeners. * * The LeakDetector will monitor and keep weak references to such collections, dump * statistics about them in a bugreport, and in the future dump the heap if their size starts * growing unreasonably. * * This should be called whenever the collection grows. * * @param tag A tag for labeling the collection in a bugreport */ public <T> void trackCollection(Collection<T> collection, String tag) { if (mTrackedCollections != null) { mTrackedCollections.track(collection, tag); } } /** * Tracks an instance that should become garbage soon. * * The LeakDetector will monitor and keep weak references to such garbage, dump * statistics about them in a bugreport, and in the future dump the heap if it is not * collected reasonably soon. * * This should be called when the last strong reference to the instance is dropped. */ public void trackGarbage(Object o) { if (mTrackedGarbage != null) { mTrackedGarbage.track(o); } } @Override public void dump(FileDescriptor df, PrintWriter w, String[] args) { IndentingPrintWriter pw = new IndentingPrintWriter(w, " "); pw.println("SYSUI LEAK DETECTOR"); pw.increaseIndent(); if (mTrackedCollections != null && mTrackedGarbage != null) { pw.println("TrackedCollections:"); pw.increaseIndent(); mTrackedCollections.dump(pw, (col) -> !TrackedObjects.isTrackedObject(col)); pw.decreaseIndent(); pw.println(); pw.println("TrackedObjects:"); pw.increaseIndent(); mTrackedCollections.dump(pw, TrackedObjects::isTrackedObject); pw.decreaseIndent(); pw.println(); pw.print("TrackedGarbage:"); pw.increaseIndent(); mTrackedGarbage.dump(pw); pw.decreaseIndent(); } else { pw.println("disabled"); } pw.decreaseIndent(); pw.println(); } public static LeakDetector create() { if (ENABLED) { TrackedCollections collections = new TrackedCollections(); return new LeakDetector(collections, new TrackedGarbage(collections), new TrackedObjects(collections)); } else { return new LeakDetector(null, null, null); } } } packages/SystemUI/src/com/android/systemui/util/leak/TrackedCollections.java 0 → 100644 +104 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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. */ package com.android.systemui.util.leak; import android.os.SystemClock; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.Collection; import java.util.Map; import java.util.function.Predicate; /** * Tracks the size of collections. */ public class TrackedCollections { private static final long MILLIS_IN_MINUTE = 60 * 1000; private static final long HALFWAY_DELAY = 30 * MILLIS_IN_MINUTE; private final WeakIdentityHashMap<Collection<?>, CollectionState> mCollections = new WeakIdentityHashMap<>(); /** * @see LeakDetector#trackCollection(Collection, String) */ public synchronized void track(Collection<?> collection, String tag) { CollectionState collectionState = mCollections.get(collection); if (collectionState == null) { collectionState = new CollectionState(); collectionState.tag = tag; collectionState.startUptime = SystemClock.uptimeMillis(); mCollections.put(collection, collectionState); } if (collectionState.halfwayCount == -1 && SystemClock.uptimeMillis() - collectionState.startUptime > HALFWAY_DELAY) { collectionState.halfwayCount = collectionState.lastCount; } collectionState.lastCount = collection.size(); collectionState.lastUptime = SystemClock.uptimeMillis(); } private static class CollectionState { String tag; long startUptime; /** The number of elements in the collection at startUptime + HALFWAY_DELAY */ int halfwayCount = -1; /** The number of elements in the collection at lastUptime */ int lastCount = -1; long lastUptime; /** * Dump statistics about the tracked collection: * - the tag * - average elements inserted per hour during * - the first 30min of its existence * - after the first 30min * - overall * - the current size of the collection */ void dump(PrintWriter pw) { long now = SystemClock.uptimeMillis(); pw.format("%s: %.2f (start-30min) / %.2f (30min-now) / %.2f (start-now)" + " (growth rate in #/hour); %d (current size)", tag, ratePerHour(startUptime, 0, startUptime + HALFWAY_DELAY, halfwayCount), ratePerHour(startUptime + HALFWAY_DELAY, halfwayCount, now, lastCount), ratePerHour(startUptime, 0, now, lastCount), lastCount); } private float ratePerHour(long uptime1, int count1, long uptime2, int count2) { if (uptime1 >= uptime2 || count1 < 0 || count2 < 0) { return Float.NaN; } return ((float) count2 - count1) / (uptime2 - uptime1) * 60 * MILLIS_IN_MINUTE; } } public synchronized void dump(PrintWriter pw, Predicate<Collection<?>> filter) { for (Map.Entry<WeakReference<Collection<?>>, CollectionState> entry : mCollections.entrySet()) { Collection<?> key = entry.getKey().get(); if (filter == null || key != null && filter.test(key)) { entry.getValue().dump(pw); pw.println(); } } } } packages/SystemUI/src/com/android/systemui/util/leak/TrackedGarbage.java 0 → 100644 +112 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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. */ package com.android.systemui.util.leak; import android.os.SystemClock; import android.util.ArrayMap; import java.io.PrintWriter; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.HashSet; import java.util.Map; /** * Tracks objects that have been marked as garbage. */ public class TrackedGarbage { /** Duration after which we consider garbage to be old. */ private static final long GARBAGE_COLLECTION_DEADLINE_MILLIS = 60000; // 1min private final HashSet<LeakReference> mGarbage = new HashSet<>(); private final ReferenceQueue<Object> mRefQueue = new ReferenceQueue<>(); private final TrackedCollections mTrackedCollections; public TrackedGarbage(TrackedCollections trackedCollections) { mTrackedCollections = trackedCollections; } /** * @see LeakDetector#trackGarbage(Object) */ public synchronized void track(Object o) { cleanUp(); mGarbage.add(new LeakReference(o, mRefQueue)); mTrackedCollections.track(mGarbage, "Garbage"); } private void cleanUp() { Reference<?> ref; while ((ref = mRefQueue.poll()) != null) { mGarbage.remove(ref); } } /** * A reference to something we consider leaked if it still has strong references. * * Helpful for finding potential leaks in a heapdump: Simply find an instance of * LeakReference, find the object it refers to, then find a strong path to a GC root. */ private static class LeakReference extends WeakReference<Object> { private final Class<?> clazz; private final long createdUptimeMillis; LeakReference(Object t, ReferenceQueue<Object> queue) { super(t, queue); clazz = t.getClass(); createdUptimeMillis = SystemClock.uptimeMillis(); } } /** * Dump statistics about the garbage. * * For each class, dumps the number of "garbage objects" that have not been collected yet. * A large number of old instances indicates a probable leak. */ public synchronized void dump(PrintWriter pw) { cleanUp(); long now = SystemClock.uptimeMillis(); ArrayMap<Class<?>, Integer> acc = new ArrayMap<>(); ArrayMap<Class<?>, Integer> accOld = new ArrayMap<>(); for (LeakReference ref : mGarbage) { acc.put(ref.clazz, acc.getOrDefault(ref.clazz, 0) + 1); if (isOld(ref.createdUptimeMillis, now)) { accOld.put(ref.clazz, accOld.getOrDefault(ref.clazz, 0) + 1); } } for (Map.Entry<Class<?>, Integer> entry : acc.entrySet()) { pw.print(entry.getKey().getName()); pw.print(": "); pw.print(entry.getValue()); pw.print(" total, "); pw.print(accOld.getOrDefault(entry.getKey(), 0)); pw.print(" old"); pw.println(); } } private boolean isOld(long createdUptimeMillis, long now) { return createdUptimeMillis + GARBAGE_COLLECTION_DEADLINE_MILLIS < now; } } Loading
packages/SystemUI/src/com/android/systemui/Dependency.java +3 −0 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.leak.LeakDetector; import java.io.FileDescriptor; import java.io.PrintWriter; Loading Loading @@ -187,6 +188,8 @@ public class Dependency extends SystemUI { mProviders.put(SecurityController.class.getName(), () -> new SecurityControllerImpl(mContext)); mProviders.put(LeakDetector.class.getName(), LeakDetector::create); mProviders.put(TunerService.class.getName(), () -> new TunerService(mContext)); Loading
packages/SystemUI/src/com/android/systemui/util/leak/AbstractCollection.java 0 → 100644 +83 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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. */ package com.android.systemui.util.leak; import java.util.Collection; import java.util.Iterator; abstract class AbstractCollection<T> implements Collection<T> { @Override public abstract int size(); @Override public abstract boolean isEmpty(); @Override public boolean contains(Object o) { throw new UnsupportedOperationException(); } @Override public Iterator<T> iterator() { throw new UnsupportedOperationException(); } @Override public Object[] toArray() { throw new UnsupportedOperationException(); } @Override public <T1> T1[] toArray(T1[] t1s) { throw new UnsupportedOperationException(); } @Override public boolean add(T t) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection<?> collection) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends T> collection) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> collection) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> collection) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } }
packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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. */ package com.android.systemui.util.leak; import android.os.Build; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.systemui.Dumpable; import java.io.FileDescriptor; import java.io.PrintWriter; import java.io.Writer; import java.util.Collection; /** * Detects leaks. */ public class LeakDetector implements Dumpable { private static final boolean ENABLED = Build.IS_DEBUGGABLE; private final TrackedCollections mTrackedCollections; private final TrackedGarbage mTrackedGarbage; private final TrackedObjects mTrackedObjects; @VisibleForTesting public LeakDetector(TrackedCollections trackedCollections, TrackedGarbage trackedGarbage, TrackedObjects trackedObjects) { mTrackedCollections = trackedCollections; mTrackedGarbage = trackedGarbage; mTrackedObjects = trackedObjects; } /** * Tracks an instance that has a high leak risk (i.e. has complex ownership and references * a large amount of memory). * * The LeakDetector will monitor and keep weak references to such instances, dump statistics * about them in a bugreport, and in the future dump the heap if their count starts growing * unreasonably. * * This should be called when the instance is first constructed. */ public <T> void trackInstance(T object) { if (mTrackedObjects != null) { mTrackedObjects.track(object); } } /** * Tracks a collection that is at risk of leaking large objects, e.g. a collection of * dynamically registered listeners. * * The LeakDetector will monitor and keep weak references to such collections, dump * statistics about them in a bugreport, and in the future dump the heap if their size starts * growing unreasonably. * * This should be called whenever the collection grows. * * @param tag A tag for labeling the collection in a bugreport */ public <T> void trackCollection(Collection<T> collection, String tag) { if (mTrackedCollections != null) { mTrackedCollections.track(collection, tag); } } /** * Tracks an instance that should become garbage soon. * * The LeakDetector will monitor and keep weak references to such garbage, dump * statistics about them in a bugreport, and in the future dump the heap if it is not * collected reasonably soon. * * This should be called when the last strong reference to the instance is dropped. */ public void trackGarbage(Object o) { if (mTrackedGarbage != null) { mTrackedGarbage.track(o); } } @Override public void dump(FileDescriptor df, PrintWriter w, String[] args) { IndentingPrintWriter pw = new IndentingPrintWriter(w, " "); pw.println("SYSUI LEAK DETECTOR"); pw.increaseIndent(); if (mTrackedCollections != null && mTrackedGarbage != null) { pw.println("TrackedCollections:"); pw.increaseIndent(); mTrackedCollections.dump(pw, (col) -> !TrackedObjects.isTrackedObject(col)); pw.decreaseIndent(); pw.println(); pw.println("TrackedObjects:"); pw.increaseIndent(); mTrackedCollections.dump(pw, TrackedObjects::isTrackedObject); pw.decreaseIndent(); pw.println(); pw.print("TrackedGarbage:"); pw.increaseIndent(); mTrackedGarbage.dump(pw); pw.decreaseIndent(); } else { pw.println("disabled"); } pw.decreaseIndent(); pw.println(); } public static LeakDetector create() { if (ENABLED) { TrackedCollections collections = new TrackedCollections(); return new LeakDetector(collections, new TrackedGarbage(collections), new TrackedObjects(collections)); } else { return new LeakDetector(null, null, null); } } }
packages/SystemUI/src/com/android/systemui/util/leak/TrackedCollections.java 0 → 100644 +104 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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. */ package com.android.systemui.util.leak; import android.os.SystemClock; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.Collection; import java.util.Map; import java.util.function.Predicate; /** * Tracks the size of collections. */ public class TrackedCollections { private static final long MILLIS_IN_MINUTE = 60 * 1000; private static final long HALFWAY_DELAY = 30 * MILLIS_IN_MINUTE; private final WeakIdentityHashMap<Collection<?>, CollectionState> mCollections = new WeakIdentityHashMap<>(); /** * @see LeakDetector#trackCollection(Collection, String) */ public synchronized void track(Collection<?> collection, String tag) { CollectionState collectionState = mCollections.get(collection); if (collectionState == null) { collectionState = new CollectionState(); collectionState.tag = tag; collectionState.startUptime = SystemClock.uptimeMillis(); mCollections.put(collection, collectionState); } if (collectionState.halfwayCount == -1 && SystemClock.uptimeMillis() - collectionState.startUptime > HALFWAY_DELAY) { collectionState.halfwayCount = collectionState.lastCount; } collectionState.lastCount = collection.size(); collectionState.lastUptime = SystemClock.uptimeMillis(); } private static class CollectionState { String tag; long startUptime; /** The number of elements in the collection at startUptime + HALFWAY_DELAY */ int halfwayCount = -1; /** The number of elements in the collection at lastUptime */ int lastCount = -1; long lastUptime; /** * Dump statistics about the tracked collection: * - the tag * - average elements inserted per hour during * - the first 30min of its existence * - after the first 30min * - overall * - the current size of the collection */ void dump(PrintWriter pw) { long now = SystemClock.uptimeMillis(); pw.format("%s: %.2f (start-30min) / %.2f (30min-now) / %.2f (start-now)" + " (growth rate in #/hour); %d (current size)", tag, ratePerHour(startUptime, 0, startUptime + HALFWAY_DELAY, halfwayCount), ratePerHour(startUptime + HALFWAY_DELAY, halfwayCount, now, lastCount), ratePerHour(startUptime, 0, now, lastCount), lastCount); } private float ratePerHour(long uptime1, int count1, long uptime2, int count2) { if (uptime1 >= uptime2 || count1 < 0 || count2 < 0) { return Float.NaN; } return ((float) count2 - count1) / (uptime2 - uptime1) * 60 * MILLIS_IN_MINUTE; } } public synchronized void dump(PrintWriter pw, Predicate<Collection<?>> filter) { for (Map.Entry<WeakReference<Collection<?>>, CollectionState> entry : mCollections.entrySet()) { Collection<?> key = entry.getKey().get(); if (filter == null || key != null && filter.test(key)) { entry.getValue().dump(pw); pw.println(); } } } }
packages/SystemUI/src/com/android/systemui/util/leak/TrackedGarbage.java 0 → 100644 +112 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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. */ package com.android.systemui.util.leak; import android.os.SystemClock; import android.util.ArrayMap; import java.io.PrintWriter; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.HashSet; import java.util.Map; /** * Tracks objects that have been marked as garbage. */ public class TrackedGarbage { /** Duration after which we consider garbage to be old. */ private static final long GARBAGE_COLLECTION_DEADLINE_MILLIS = 60000; // 1min private final HashSet<LeakReference> mGarbage = new HashSet<>(); private final ReferenceQueue<Object> mRefQueue = new ReferenceQueue<>(); private final TrackedCollections mTrackedCollections; public TrackedGarbage(TrackedCollections trackedCollections) { mTrackedCollections = trackedCollections; } /** * @see LeakDetector#trackGarbage(Object) */ public synchronized void track(Object o) { cleanUp(); mGarbage.add(new LeakReference(o, mRefQueue)); mTrackedCollections.track(mGarbage, "Garbage"); } private void cleanUp() { Reference<?> ref; while ((ref = mRefQueue.poll()) != null) { mGarbage.remove(ref); } } /** * A reference to something we consider leaked if it still has strong references. * * Helpful for finding potential leaks in a heapdump: Simply find an instance of * LeakReference, find the object it refers to, then find a strong path to a GC root. */ private static class LeakReference extends WeakReference<Object> { private final Class<?> clazz; private final long createdUptimeMillis; LeakReference(Object t, ReferenceQueue<Object> queue) { super(t, queue); clazz = t.getClass(); createdUptimeMillis = SystemClock.uptimeMillis(); } } /** * Dump statistics about the garbage. * * For each class, dumps the number of "garbage objects" that have not been collected yet. * A large number of old instances indicates a probable leak. */ public synchronized void dump(PrintWriter pw) { cleanUp(); long now = SystemClock.uptimeMillis(); ArrayMap<Class<?>, Integer> acc = new ArrayMap<>(); ArrayMap<Class<?>, Integer> accOld = new ArrayMap<>(); for (LeakReference ref : mGarbage) { acc.put(ref.clazz, acc.getOrDefault(ref.clazz, 0) + 1); if (isOld(ref.createdUptimeMillis, now)) { accOld.put(ref.clazz, accOld.getOrDefault(ref.clazz, 0) + 1); } } for (Map.Entry<Class<?>, Integer> entry : acc.entrySet()) { pw.print(entry.getKey().getName()); pw.print(": "); pw.print(entry.getValue()); pw.print(" total, "); pw.print(accOld.getOrDefault(entry.getKey(), 0)); pw.print(" old"); pw.println(); } } private boolean isOld(long createdUptimeMillis, long now) { return createdUptimeMillis + GARBAGE_COLLECTION_DEADLINE_MILLIS < now; } }