// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 package com.google.appinventor.components.runtime.util; import com.google.appinventor.components.runtime.collect.Maps; import android.util.Log; import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; /** * MemoryLeakUtil provides some useful methods for detecting memory leaks. * * @author lizlooney@google.com (Liz Looney) */ public class MemoryLeakUtil { private static final String LOG_TAG = "MemoryLeakUtil"; private static final AtomicInteger prefixGenerator = new AtomicInteger(0); private static final Map<String, WeakReference<Object>> TRACKED_OBJECTS = Maps.newTreeMap(); private MemoryLeakUtil() { } /** * Tracks the given Object by saving a WeakReference to it so that we can check later if it has * been garbage collected. * * @param tag the tag for the Object * @param object the Object to track * @return the key that is used to track the Object */ public static String trackObject(String tag, Object object) { String key = (tag == null) ? prefixGenerator.incrementAndGet() + "_" : prefixGenerator.incrementAndGet() + "_" + tag; TRACKED_OBJECTS.put(key, new WeakReference<Object>(object)); return key; } /** * Checks whether a particular tracked object has been garbage collected. * * @param key a key returned from {@link #trackObject} * @param stopTrackingIfCollected whether to stop tracking the object if it has been garbage * collected * @return true if the tracked object has been collected; false otherwise * @throws IllegalArgumentException if the key is not valid */ public static boolean isTrackedObjectCollected(String key, boolean stopTrackingIfCollected) { System.gc(); WeakReference<Object> ref = TRACKED_OBJECTS.get(key); if (ref != null) { Object o = ref.get(); String tag = key.substring(key.indexOf("_") + 1); Log.i(LOG_TAG, "Object with tag " + tag + " has " + ((o != null) ? "not " : "") + "been garbage collected."); if (stopTrackingIfCollected && o == null) { TRACKED_OBJECTS.remove(key); } return o == null; } throw new IllegalArgumentException("key not found"); } /** * Checks whether all tracked objects have been garbage collected, logging summary information * including how many have been collected and how many have not. Optionally, it can log details * showing which objects have been garbage collected and which have not. * * @param verbose whether to log details about each tracked object * @param stopTrackingCollectedObjects whether to stop tracking objects that have been garbage * collected */ public static void checkAllTrackedObjects(boolean verbose, boolean stopTrackingCollectedObjects) { Log.i(LOG_TAG, "Checking Tracked Objects ----------------------------------------"); System.gc(); int countRemaining = 0; int countCollected = 0; for (Iterator<Map.Entry<String, WeakReference<Object>>> it = TRACKED_OBJECTS.entrySet().iterator(); it.hasNext();) { Map.Entry<String, WeakReference<Object>> entry = it.next(); String key = entry.getKey(); WeakReference<Object> ref = entry.getValue(); Object o = ref.get(); if (o != null) { countRemaining++; } else { countCollected++; if (stopTrackingCollectedObjects) { it.remove(); } } if (verbose) { String tag = key.substring(key.indexOf("_") + 1); Log.i(LOG_TAG, "Object with tag " + tag + " has " + ((o != null) ? "not " : "") + "been garbage collected."); } } Log.i(LOG_TAG, "summary: collected " + countCollected); Log.i(LOG_TAG, "summary: remaining " + countRemaining); Log.i(LOG_TAG, "-----------------------------------------------------------------"); } }