1 /*
2  * Copyright (C) 2017 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 package com.android.systemui.util.leak;
18 
19 import android.os.SystemClock;
20 import android.util.ArrayMap;
21 
22 import java.io.PrintWriter;
23 import java.lang.ref.Reference;
24 import java.lang.ref.ReferenceQueue;
25 import java.lang.ref.WeakReference;
26 import java.util.HashSet;
27 import java.util.Map;
28 
29 /**
30  * Tracks objects that have been marked as garbage.
31  */
32 public class TrackedGarbage {
33 
34     /** Duration after which we consider garbage to be old. */
35     private static final long GARBAGE_COLLECTION_DEADLINE_MILLIS = 60000; // 1min
36 
37     private final HashSet<LeakReference> mGarbage = new HashSet<>();
38     private final ReferenceQueue<Object> mRefQueue = new ReferenceQueue<>();
39     private final TrackedCollections mTrackedCollections;
40 
TrackedGarbage(TrackedCollections trackedCollections)41     public TrackedGarbage(TrackedCollections trackedCollections) {
42         mTrackedCollections = trackedCollections;
43     }
44 
45     /**
46      * @see LeakDetector#trackGarbage(Object)
47      */
track(Object o)48     public synchronized void track(Object o) {
49         cleanUp();
50         mGarbage.add(new LeakReference(o, mRefQueue));
51         mTrackedCollections.track(mGarbage, "Garbage");
52     }
53 
cleanUp()54     private void cleanUp() {
55         Reference<?> ref;
56         while ((ref = mRefQueue.poll()) != null) {
57             mGarbage.remove(ref);
58         }
59     }
60 
61     /**
62      * A reference to something we consider leaked if it still has strong references.
63      *
64      * Helpful for finding potential leaks in a heapdump: Simply find an instance of
65      * LeakReference, find the object it refers to, then find a strong path to a GC root.
66      */
67     private static class LeakReference extends WeakReference<Object> {
68         private final Class<?> clazz;
69         private final long createdUptimeMillis;
70 
LeakReference(Object t, ReferenceQueue<Object> queue)71         LeakReference(Object t, ReferenceQueue<Object> queue) {
72             super(t, queue);
73             clazz = t.getClass();
74             createdUptimeMillis = SystemClock.uptimeMillis();
75         }
76     }
77 
78     /**
79      * Dump statistics about the garbage.
80      *
81      * For each class, dumps the number of "garbage objects" that have not been collected yet.
82      * A large number of old instances indicates a probable leak.
83      */
dump(PrintWriter pw)84     public synchronized void dump(PrintWriter pw) {
85         cleanUp();
86 
87         long now = SystemClock.uptimeMillis();
88 
89         ArrayMap<Class<?>, Integer> acc = new ArrayMap<>();
90         ArrayMap<Class<?>, Integer> accOld = new ArrayMap<>();
91         for (LeakReference ref : mGarbage) {
92             acc.put(ref.clazz, acc.getOrDefault(ref.clazz, 0) + 1);
93             if (isOld(ref.createdUptimeMillis, now)) {
94                 accOld.put(ref.clazz, accOld.getOrDefault(ref.clazz, 0) + 1);
95             }
96         }
97 
98         for (Map.Entry<Class<?>, Integer> entry : acc.entrySet()) {
99             pw.print(entry.getKey().getName());
100             pw.print(": ");
101             pw.print(entry.getValue());
102             pw.print(" total, ");
103             pw.print(accOld.getOrDefault(entry.getKey(), 0));
104             pw.print(" old");
105             pw.println();
106         }
107     }
108 
countOldGarbage()109     public synchronized int countOldGarbage() {
110         cleanUp();
111 
112         long now = SystemClock.uptimeMillis();
113 
114         int result = 0;
115         for (LeakReference ref : mGarbage) {
116             if (isOld(ref.createdUptimeMillis, now)) {
117                 result++;
118             }
119         }
120         return result;
121     }
122 
isOld(long createdUptimeMillis, long now)123     private boolean isOld(long createdUptimeMillis, long now) {
124         return createdUptimeMillis + GARBAGE_COLLECTION_DEADLINE_MILLIS < now;
125     }
126 }
127