private Instance findLeakingReference(String key, Snapshot snapshot) { ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName()); List<String> keysFound = new ArrayList<>(); for (Instance instance : refClass.getInstancesList()) { List<ClassInstance.FieldValue> values = classInstanceValues(instance); String keyCandidate = asString(fieldValue(values, "key")); if (keyCandidate.equals(key)) { return fieldValue(values, "referent"); } keysFound.add(keyCandidate); } throw new IllegalStateException( "Could not find weak reference with key " + key + " in " + keysFound); }
/** * Bitmaps and bitmap byte arrays are sometimes held by native gc roots, so they aren't included * in the retained size because their root dominator is a native gc root. To fix this, we check if * the leaking instance is a dominator for each bitmap instance and then add the bitmap size. * * <p>From experience, we've found that bitmap created in code (Bitmap.createBitmap()) are * correctly accounted for, however bitmaps set in layouts are not. */ private int computeIgnoredBitmapRetainedSize(Snapshot snapshot, Instance leakingInstance) { int bitmapRetainedSize = 0; ClassObj bitmapClass = snapshot.findClass("android.graphics.Bitmap"); for (Instance bitmapInstance : bitmapClass.getInstancesList()) { if (isIgnoredDominator(leakingInstance, bitmapInstance)) { ArrayInstance mBufferInstance = fieldValue(classInstanceValues(bitmapInstance), "mBuffer"); // Native bitmaps have mBuffer set to null. We sadly can't account for them. if (mBufferInstance == null) { continue; } long bufferSize = mBufferInstance.getTotalRetainedSize(); long bitmapSize = bitmapInstance.getTotalRetainedSize(); // Sometimes the size of the buffer isn't accounted for in the bitmap retained size. Since // the buffer is large, it's easy to detect by checking for bitmap size < buffer size. if (bitmapSize < bufferSize) { bitmapSize += bufferSize; } bitmapRetainedSize += bitmapSize; } } return bitmapRetainedSize; }
private LeakTraceElement buildLeakElement(LeakNode node) { if (node.parent == null) { // Ignore any root node. return null; } Instance holder = node.parent.instance; if (holder instanceof RootObj) { return null; } LeakTraceElement.Type type = node.referenceType; String referenceName = node.referenceName; LeakTraceElement.Holder holderType; String className; String extra = null; List<String> fields = new ArrayList<>(); if (holder instanceof ClassObj) { ClassObj classObj = (ClassObj) holder; holderType = CLASS; className = classObj.getClassName(); for (Map.Entry<Field, Object> entry : classObj.getStaticFieldValues().entrySet()) { Field field = entry.getKey(); Object value = entry.getValue(); fields.add("static " + field.getName() + " = " + value); } } else if (holder instanceof ArrayInstance) { ArrayInstance arrayInstance = (ArrayInstance) holder; holderType = ARRAY; className = arrayInstance.getClassObj().getClassName(); if (arrayInstance.getArrayType() == Type.OBJECT) { Object[] values = arrayInstance.getValues(); for (int i = 0; i < values.length; i++) { fields.add("[" + i + "] = " + values[i]); } } } else { ClassInstance classInstance = (ClassInstance) holder; ClassObj classObj = holder.getClassObj(); for (Map.Entry<Field, Object> entry : classObj.getStaticFieldValues().entrySet()) { fields.add("static " + fieldToString(entry)); } for (ClassInstance.FieldValue field : classInstance.getValues()) { fields.add(fieldToString(field)); } className = classObj.getClassName(); if (extendsThread(classObj)) { holderType = THREAD; String threadName = threadName(holder); extra = "(named '" + threadName + "')"; } else if (className.matches(ANONYMOUS_CLASS_NAME_PATTERN)) { String parentClassName = classObj.getSuperClassObj().getClassName(); if (Object.class.getName().equals(parentClassName)) { holderType = OBJECT; try { // This is an anonymous class implementing an interface. The API does not give access // to the interfaces implemented by the class. We check if it's in the class path and // use that instead. Class<?> actualClass = Class.forName(classObj.getClassName()); Class<?>[] interfaces = actualClass.getInterfaces(); if (interfaces.length > 0) { Class<?> implementedInterface = interfaces[0]; extra = "(anonymous implementation of " + implementedInterface.getName() + ")"; } else { extra = "(anonymous subclass of java.lang.Object)"; } } catch (ClassNotFoundException ignored) { } } else { holderType = OBJECT; // Makes it easier to figure out which anonymous class we're looking at. extra = "(anonymous subclass of " + parentClassName + ")"; } } else { holderType = OBJECT; } } return new LeakTraceElement( referenceName, type, holderType, className, extra, node.exclusion, fields); }