diff --git a/DuplicatedBitmapAnalyzer/build.gradle b/DuplicatedBitmapAnalyzer/build.gradle index 912e192..b3ca8fa 100644 --- a/DuplicatedBitmapAnalyzer/build.gradle +++ b/DuplicatedBitmapAnalyzer/build.gradle @@ -5,34 +5,12 @@ apply plugin: 'java' version 1.0 dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') -} - - -jar { - manifest { - attributes 'Main-Class': 'com.hprof.bitmap.Main' - attributes 'Manifest-Version': version - } + implementation fileTree(include: ['*.aar'], dir: 'libs') + implementation 'com.squareup.haha:haha:2.0.4' + implementation 'com.alibaba:fastjson:1.2.54' + // https://mvnrepository.com/artifact/com.squareup.leakcanary/leakcanary-watcher + implementation group: 'com.squareup.leakcanary', name: 'leakcanary-watcher', version: '1.6.2' - from { - exclude 'META-INF/MANIFEST.MF' - exclude 'META-INF/*.SF' - exclude 'META-INF/*.DSA' - exclude 'META-INF/*.RSA' - configurations.runtime.resolve().collect { - it.isDirectory() ? it : zipTree(it) - } - } } -// copy the jar to work directory -task buildAlloctrackJar(type: Copy, dependsOn: [build, jar]) { - group = "buildTool" - from('build/libs') { - include '*.jar' - exclude '*-javadoc.jar' - exclude '*-sources.jar' - } - into(rootProject.file("tools")) -} + diff --git a/DuplicatedBitmapAnalyzer/libs/classes.jar b/DuplicatedBitmapAnalyzer/libs/classes.jar new file mode 100644 index 0000000..e60ee0e Binary files /dev/null and b/DuplicatedBitmapAnalyzer/libs/classes.jar differ diff --git a/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/Analyzer.java b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/Analyzer.java new file mode 100644 index 0000000..83057be --- /dev/null +++ b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/Analyzer.java @@ -0,0 +1,133 @@ +package com.hprof.bitmap; + +import com.alibaba.fastjson.JSON; +import com.squareup.haha.perflib.*; +import com.squareup.haha.perflib.io.HprofBuffer; +import com.squareup.haha.perflib.io.MemoryMappedFileBuffer; +import jdk.nashorn.internal.ir.WhileNode; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class Analyzer { + private HashMap byteArrayToBitmapMap = new HashMap<>(); + Set byteArrays = new HashSet<>(); + private Snapshot snapshot; + ShortestPathFinder shortestPathFinder = new ShortestPathFinder(null); + private List duplicateResults = new ArrayList<>(); + + private HashMap cacheValuesMap = new HashMap<>(); + + public void find() throws IOException { + System.out.println(); + File heapFile = new File(System.getProperty("user.dir") + "\\DuplicatedBitmapAnalyzer\\res\\hprof\\1.hprof"); + HprofBuffer hprofBuffer = new MemoryMappedFileBuffer(heapFile); + HprofParser parser = new HprofParser(hprofBuffer); + snapshot = parser.parse(); + ClassObj bitmapClass = snapshot.findClass("android.graphics.Bitmap"); + List bitmapInstances = getBitmapInstances(snapshot, bitmapClass); + bitmapInstances.stream().forEach(instance -> { + ArrayInstance arrayInstance = HahaHelper.fieldValue(((ClassInstance) instance).getValues(), "mBuffer"); + byteArrayToBitmapMap.put(arrayInstance, instance); + + }); + byteArrays.addAll(byteArrayToBitmapMap.keySet()); + + + // 缓存Bitmap的数组 + cacheValuesMap = new HashMap<>(byteArrays.size()); + byteArrays.forEach(arrayInstance -> { + cacheValuesMap.put(arrayInstance, arrayInstance.getValues()); + }); + + // 根据Bitmap数组长度进行分类 + Map> arrayInstanceMapBySize = + byteArrays.stream().collect(Collectors.groupingBy(ArrayInstance::getSize, + Collectors.mapping(Function.identity(), Collectors.toSet()))); + + // bitmap数组大小不同的判定为非重复图片,移除 + arrayInstanceMapBySize.keySet().stream() + .filter(key -> arrayInstanceMapBySize.get(key).size() > 1) + .forEach(size -> compareBitmapIsSame(arrayInstanceMapBySize.get(size), size)); + } + + + /* + * 通过比较两个Bitmap的hashcode + */ + private void compareBitmapIsSame(Set bitmaps, int bitmapArrayLength) { + Map> prefixMap = new HashMap<>(); + + for (int column = 0; column < bitmapArrayLength; column++) { + prefixMap.clear(); + for (ArrayInstance arrayInstance : bitmaps) { + Object[] bitmapArray = cacheValuesMap.get(arrayInstance); + + if (prefixMap.containsKey(bitmapArray[column])) { + prefixMap.get(bitmapArray[column]).add(arrayInstance); + } else { + Set prefixBitmapArrays = new HashSet<>(); + prefixBitmapArrays.add(arrayInstance); + prefixMap.put(bitmapArray[column], prefixBitmapArrays); + } + } + + // 每次移除 + prefixMap.forEach((key, value) -> { + if (value.size() < 2) { + bitmaps.remove(value.toArray()[0]); + } + }); + } + + prefixMap.forEach((key, arrayInstances) -> arrayInstances.forEach(arrayInstance -> { + if (arrayInstances.size() > 1) { + duplicateResults.add(getDuplicateResult(byteArrayToBitmapMap.get(arrayInstance), arrayInstances.size())); + } + })); + System.out.println(JSON.toJSONString(duplicateResults)); + } + + /* + * art虚拟机堆存储区域分为default、app、image、zygote, 这里只分析在app heap中的bitmap + * 整个过程:先拿到Bitmap的类对象,根据类对象获取到堆里面的对象实例 + */ + private List getBitmapInstances(Snapshot snapshot, ClassObj bitmapClass) { + List reachableInstances = new ArrayList<>(); + snapshot.getHeaps() + .stream() + .filter(heap -> "app".equals(heap.getName())) + .forEach(heap -> bitmapClass + .getHeapInstances(heap.getId()) + .stream() +// .filter(instance -> instance.getDistanceToGcRoot() != Integer.MAX_VALUE) + .forEach(instance -> { + reachableInstances.add(instance); + })); + return reachableInstances; + } + + /** + * 打印堆栈信息 + */ + private DuplicateResult getDuplicateResult(Instance instance, int size) { + DuplicateResult duplicateResult = new DuplicateResult(); + duplicateResult.setDuplcateCount(size); + duplicateResult.setHeight(HahaHelper.fieldValue(((ClassInstance) instance).getValues(), "mHeight")); + duplicateResult.setWidth(HahaHelper.fieldValue(((ClassInstance) instance).getValues(), "mWidth")); + ShortestPathFinder.Result result = shortestPathFinder.findPath(snapshot, instance); + LeakNode leakNode = result.leakingNode; + List stackInfo = new ArrayList<>(); + while (leakNode != null) { + stackInfo.add(leakNode.instance.toString()); + leakNode = leakNode.parent; + } + duplicateResult.setStack(stackInfo); + duplicateResult.setBufferHash(instance.hashCode()); + duplicateResult.getBufferSize(instance.getSize()); + return duplicateResult; + } +} diff --git a/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/DuplicateResult.java b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/DuplicateResult.java new file mode 100644 index 0000000..6e0b230 --- /dev/null +++ b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/DuplicateResult.java @@ -0,0 +1,65 @@ +package com.hprof.bitmap; + +import java.util.List; + +public class DuplicateResult { + private int duplcateCount; + private List stack; + private int bufferHash; + private int width; + private int height; + private long bufferSize; + + public int getDuplcateCount() { + return duplcateCount; + } + + public void setDuplcateCount(int duplcateCount) { + this.duplcateCount = duplcateCount; + } + + public List getStack() { + return stack; + } + + public void setStack(List stack) { + this.stack = stack; + } + + public int getBufferHash() { + return bufferHash; + } + + public void setBufferHash(int bufferHash) { + this.bufferHash = bufferHash; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public long getBufferSize(int size) { + return bufferSize; + } + + public void setBufferSize(long bufferSize) { + this.bufferSize = bufferSize; + } + + @Override + public String toString() { + return super.toString(); + } +} diff --git a/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/HahaHelper.java b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/HahaHelper.java new file mode 100644 index 0000000..2f1b81d --- /dev/null +++ b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/HahaHelper.java @@ -0,0 +1,197 @@ +package com.hprof.bitmap;/* + * Copyright (C) 2015 Square, Inc. + * + * 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. + */ + +import com.squareup.haha.perflib.ArrayInstance; +import com.squareup.haha.perflib.ClassInstance; +import com.squareup.haha.perflib.ClassObj; +import com.squareup.haha.perflib.Instance; +import com.squareup.haha.perflib.Type; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import static java.util.Arrays.asList; + +public final class HahaHelper { + + private static final Set WRAPPER_TYPES = new HashSet<>( + asList(Boolean.class.getName(), Character.class.getName(), Float.class.getName(), + Double.class.getName(), Byte.class.getName(), Short.class.getName(), + Integer.class.getName(), Long.class.getName())); + + static String threadName(Instance holder) { + List values = classInstanceValues(holder); + Object nameField = fieldValue(values, "name"); + if (nameField == null) { + // Sometimes we can't find the String at the expected memory address in the heap dump. + // See https://github.com/square/leakcanary/issues/417 . + return "Thread name not available"; + } + return asString(nameField); + } + + static boolean extendsThread(ClassObj clazz) { + boolean extendsThread = false; + ClassObj parentClass = clazz; + while (parentClass.getSuperClassObj() != null) { + if (parentClass.getClassName().equals(Thread.class.getName())) { + extendsThread = true; + break; + } + parentClass = parentClass.getSuperClassObj(); + } + return extendsThread; + } + + /** + * This returns a string representation of any object or value passed in. + */ + static String valueAsString(Object value) { + String stringValue; + if (value == null) { + stringValue = "null"; + } else if (value instanceof ClassInstance) { + String valueClassName = ((ClassInstance) value).getClassObj().getClassName(); + if (valueClassName.equals(String.class.getName())) { + stringValue = '"' + asString(value) + '"'; + } else { + stringValue = value.toString(); + } + } else { + stringValue = value.toString(); + } + return stringValue; + } + + /** Given a string instance from the heap dump, this returns its actual string value. */ + static String asString(Object stringObject) { + checkNotNull(stringObject, "stringObject"); + Instance instance = (Instance) stringObject; + List values = classInstanceValues(instance); + + Integer count = fieldValue(values, "count"); + checkNotNull(count, "count"); + if (count == 0) { + return ""; + } + + Object value = fieldValue(values, "value"); + checkNotNull(value, "value"); + + Integer offset; + ArrayInstance array; + if (isCharArray(value)) { + array = (ArrayInstance) value; + + offset = 0; + // < API 23 + // As of Marshmallow, substrings no longer share their parent strings' char arrays + // eliminating the need for String.offset + // https://android-review.googlesource.com/#/c/83611/ + if (hasField(values, "offset")) { + offset = fieldValue(values, "offset"); + checkNotNull(offset, "offset"); + } + + char[] chars = array.asCharArray(offset, count); + return new String(chars); + } else if (isByteArray(value)) { + // In API 26, Strings are now internally represented as byte arrays. + array = (ArrayInstance) value; + + // HACK - remove when HAHA's perflib is updated to https://goo.gl/Oe7ZwO. + try { + Method asRawByteArray = + ArrayInstance.class.getDeclaredMethod("asRawByteArray", int.class, int.class); + asRawByteArray.setAccessible(true); + byte[] rawByteArray = (byte[]) asRawByteArray.invoke(array, 0, count); + return new String(rawByteArray, Charset.forName("UTF-8")); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } else { + throw new UnsupportedOperationException("Could not find char array in " + instance); + } + } + + public static boolean isPrimitiveWrapper(Object value) { + if (!(value instanceof ClassInstance)) { + return false; + } + return WRAPPER_TYPES.contains(((ClassInstance) value).getClassObj().getClassName()); + } + + public static boolean isPrimitiveOrWrapperArray(Object value) { + if (!(value instanceof ArrayInstance)) { + return false; + } + ArrayInstance arrayInstance = (ArrayInstance) value; + if (arrayInstance.getArrayType() != Type.OBJECT) { + return true; + } + return WRAPPER_TYPES.contains(arrayInstance.getClassObj().getClassName()); + } + + private static boolean isCharArray(Object value) { + return value instanceof ArrayInstance && ((ArrayInstance) value).getArrayType() == Type.CHAR; + } + + private static boolean isByteArray(Object value) { + return value instanceof ArrayInstance && ((ArrayInstance) value).getArrayType() == Type.BYTE; + } + + static List classInstanceValues(Instance instance) { + ClassInstance classInstance = (ClassInstance) instance; + return classInstance.getValues(); + } + + @SuppressWarnings({ "unchecked", "TypeParameterUnusedInFormals" }) + static T fieldValue(List values, String fieldName) { + for (ClassInstance.FieldValue fieldValue : values) { + if (fieldValue.getField().getName().equals(fieldName)) { + return (T) fieldValue.getValue(); + } + } + throw new IllegalArgumentException("Field " + fieldName + " does not exists"); + } + + static boolean hasField(List values, String fieldName) { + for (ClassInstance.FieldValue fieldValue : values) { + if (fieldValue.getField().getName().equals(fieldName)) { + //noinspection unchecked + return true; + } + } + return false; + } + + private HahaHelper() { + throw new AssertionError(); + } + + static T checkNotNull(T instance, String name) { + if (instance == null) { + throw new NullPointerException(name + " must not be null"); + } + return instance; + } +} diff --git a/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/ImageUtil.java b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/ImageUtil.java new file mode 100644 index 0000000..53f9427 --- /dev/null +++ b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/ImageUtil.java @@ -0,0 +1,24 @@ +package com.hprof.bitmap; + +import javax.imageio.ImageIO; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; + +public class ImageUtil { + public static void byteToImage(Object[] buffers) { + + try { + File image = new File(System.getProperty("user.dir") + +"\\DuplicatedBitmapAnalyzer\\res\\image"+System.currentTimeMillis()+".jpg"); + byte[] bytes = new byte[buffers.length]; + for (int i = 0; i < buffers.length; i++) { + bytes[i] = (byte) buffers[i]; + } + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + ImageIO.write(ImageIO.read(bais), "jpg",image); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/LeakNode.java b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/LeakNode.java new file mode 100644 index 0000000..6c7862b --- /dev/null +++ b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/LeakNode.java @@ -0,0 +1,33 @@ +package com.hprof.bitmap;/* + * Copyright (C) 2015 Square, Inc. + * + * 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. + */ + +import com.squareup.haha.perflib.Instance; +import com.squareup.leakcanary.Exclusion; +import com.squareup.leakcanary.LeakReference; + +final class LeakNode { + /** May be null. */ + final Exclusion exclusion; + final Instance instance; + final LeakNode parent; + final LeakReference leakReference; + LeakNode(Exclusion exclusion, Instance instance, LeakNode parent, LeakReference leakReference) { + this.exclusion = exclusion; + this.instance = instance; + this.parent = parent; + this.leakReference = leakReference; + } +} diff --git a/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/Main.java b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/Main.java index 202764e..1ebbded 100644 --- a/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/Main.java +++ b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/Main.java @@ -1,13 +1,11 @@ package com.hprof.bitmap; -import java.io.FileInputStream; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.util.ArrayList; public class Main { public static void main(String[] args) throws IOException { + Analyzer analyzer = new Analyzer(); + analyzer.find(); } } diff --git a/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/ShortestPathFinder.java b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/ShortestPathFinder.java new file mode 100644 index 0000000..2d5a74e --- /dev/null +++ b/DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/ShortestPathFinder.java @@ -0,0 +1,264 @@ +package com.hprof.bitmap;/* + * Copyright (C) 2015 Square, Inc. + * + * 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. + */ + +import com.squareup.haha.perflib.*; +import com.squareup.leakcanary.ExcludedRefs; +import com.squareup.leakcanary.Exclusion; +import com.squareup.leakcanary.LeakReference; + +import java.util.*; + +import static com.squareup.leakcanary.LeakTraceElement.Type.*; + +/** + * Not thread safe. + * + * Finds the shortest path from a leaking reference to a gc root, ignoring excluded + * refs first and then including the ones that are not "always ignorable" as needed if no path is + * found. + */ +final class ShortestPathFinder { + + private final ExcludedRefs excludedRefs; + private final Deque toVisitQueue; + private final Deque toVisitIfNoPathQueue; + private final LinkedHashSet toVisitSet; + private final LinkedHashSet toVisitIfNoPathSet; + private final LinkedHashSet visitedSet; + private boolean canIgnoreStrings; + + ShortestPathFinder(ExcludedRefs excludedRefs) { + this.excludedRefs = excludedRefs; + toVisitQueue = new ArrayDeque<>(); + toVisitIfNoPathQueue = new ArrayDeque<>(); + toVisitSet = new LinkedHashSet<>(); + toVisitIfNoPathSet = new LinkedHashSet<>(); + visitedSet = new LinkedHashSet<>(); + } + + static final class Result { + final LeakNode leakingNode; + final boolean excludingKnownLeaks; + + Result(LeakNode leakingNode, boolean excludingKnownLeaks) { + this.leakingNode = leakingNode; + this.excludingKnownLeaks = excludingKnownLeaks; + } + } + + Result findPath(Snapshot snapshot, Instance leakingRef) { + clearState(); + canIgnoreStrings = !isString(leakingRef); + + enqueueGcRoots(snapshot); + + boolean excludingKnownLeaks = false; + LeakNode leakingNode = null; + while (!toVisitQueue.isEmpty() || !toVisitIfNoPathQueue.isEmpty()) { + LeakNode node; + if (!toVisitQueue.isEmpty()) { + node = toVisitQueue.poll(); + } else { + node = toVisitIfNoPathQueue.poll(); + if (node.exclusion == null) { + throw new IllegalStateException("Expected node to have an exclusion " + node); + } + excludingKnownLeaks = true; + } + + // Termination + if (node.instance == leakingRef) { + leakingNode = node; + break; + } + + if (checkSeen(node)) { + continue; + } + + if (node.instance instanceof RootObj) { + visitRootObj(node); + } else if (node.instance instanceof ClassObj) { + visitClassObj(node); + } else if (node.instance instanceof ClassInstance) { + visitClassInstance(node); + } else if (node.instance instanceof ArrayInstance) { + visitArrayInstance(node); + } else { + throw new IllegalStateException("Unexpected type for " + node.instance); + } + } + return new Result(leakingNode, excludingKnownLeaks); + } + + private void clearState() { + toVisitQueue.clear(); + toVisitIfNoPathQueue.clear(); + toVisitSet.clear(); + toVisitIfNoPathSet.clear(); + visitedSet.clear(); + } + + private void enqueueGcRoots(Snapshot snapshot) { + for (RootObj rootObj : snapshot.getGCRoots()) { + switch (rootObj.getRootType()) { + case JAVA_LOCAL: + case INTERNED_STRING: + case DEBUGGER: + case INVALID_TYPE: + // An object that is unreachable from any other root, but not a root itself. + case UNREACHABLE: + case UNKNOWN: + // An object that is in a queue, waiting for a finalizer to run. + case FINALIZING: + break; + case SYSTEM_CLASS: + case VM_INTERNAL: + // A local variable in native code. + case NATIVE_LOCAL: + // A global variable in native code. + case NATIVE_STATIC: + // An object that was referenced from an active thread block. + case THREAD_BLOCK: + // Everything that called the wait() or notify() methods, or that is synchronized. + case BUSY_MONITOR: + case NATIVE_MONITOR: + case REFERENCE_CLEANUP: + // Input or output parameters in native code. + case NATIVE_STACK: + case JAVA_STATIC: + enqueue(null, null, rootObj, null); + break; + default: + throw new UnsupportedOperationException("Unknown root type:" + rootObj.getRootType()); + } + } + } + + private boolean checkSeen(LeakNode node) { + return !visitedSet.add(node.instance); + } + + private void visitRootObj(LeakNode node) { + RootObj rootObj = (RootObj) node.instance; + Instance child = rootObj.getReferredInstance(); + + if (rootObj.getRootType() == RootType.JAVA_LOCAL) { + Instance holder = HahaSpy.allocatingThread(rootObj); + // We switch the parent node with the thread instance that holds + // the local reference. + Exclusion exclusion = null; + if (node.exclusion != null) { + exclusion = node.exclusion; + } + LeakNode parent = new LeakNode(null, holder, null, null); + enqueue(exclusion, parent, child, new LeakReference(LOCAL, null, null)); + } else { + enqueue(null, node, child, null); + } + } + + private void visitClassObj(LeakNode node) { + ClassObj classObj = (ClassObj) node.instance; + for (Map.Entry entry : classObj.getStaticFieldValues().entrySet()) { + Field field = entry.getKey(); + if (field.getType() != Type.OBJECT) { + continue; + } + String fieldName = field.getName(); + if (fieldName.equals("$staticOverhead")) { + continue; + } + Instance child = (Instance) entry.getValue(); + String fieldValue = entry.getValue() == null ? "null" : entry.getValue().toString(); + LeakReference leakReference = new LeakReference(STATIC_FIELD, fieldName, fieldValue); + enqueue(null, node, child, leakReference); + } + } + + private void visitClassInstance(LeakNode node) { + ClassInstance classInstance = (ClassInstance) node.instance; + ClassObj superClassObj = classInstance.getClassObj(); + while (superClassObj != null) { + + superClassObj = superClassObj.getSuperClassObj(); + } + + for (ClassInstance.FieldValue fieldValue : classInstance.getValues()) { + Field field = fieldValue.getField(); + if (field.getType() != Type.OBJECT) { + continue; + } + Instance child = (Instance) fieldValue.getValue(); + String fieldName = field.getName(); + // If we found a field exclusion and it's stronger than a class exclusion + String value = fieldValue.getValue() == null ? "null" : fieldValue.getValue().toString(); + enqueue(null, node, child, new LeakReference(INSTANCE_FIELD, fieldName, value)); + } + } + + private void visitArrayInstance(LeakNode node) { + ArrayInstance arrayInstance = (ArrayInstance) node.instance; + Type arrayType = arrayInstance.getArrayType(); + if (arrayType == Type.OBJECT) { + Object[] values = arrayInstance.getValues(); + for (int i = 0; i < values.length; i++) { + Instance child = (Instance) values[i]; + String name = Integer.toString(i); + String value = child == null ? "null" : child.toString(); + enqueue(null, node, child, new LeakReference(ARRAY_ENTRY, name, value)); + } + } + } + + private void enqueue(Exclusion exclusion, LeakNode parent, Instance child, + LeakReference leakReference) { + if (child == null) { + return; + } + if (HahaHelper.isPrimitiveOrWrapperArray(child) || HahaHelper.isPrimitiveWrapper(child)) { + return; + } + // Whether we want to visit now or later, we should skip if this is already to visit. + if (toVisitSet.contains(child)) { + return; + } + boolean visitNow = exclusion == null; + if (!visitNow && toVisitIfNoPathSet.contains(child)) { + return; + } + if (canIgnoreStrings && isString(child)) { + return; + } + if (visitedSet.contains(child)) { + return; + } + LeakNode childNode = new LeakNode(exclusion, child, parent, leakReference); + if (visitNow) { + toVisitSet.add(child); + toVisitQueue.add(childNode); + } else { + toVisitIfNoPathSet.add(child); + toVisitIfNoPathQueue.add(childNode); + } + } + + private boolean isString(Instance instance) { + return instance.getClassObj() != null && instance.getClassObj() + .getClassName() + .equals(String.class.getName()); + } +} diff --git a/build.gradle b/build.gradle index cdf1d5d..e53c987 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { repositories { google() - jcenter() + maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'} } dependencies { @@ -17,7 +17,7 @@ buildscript { allprojects { repositories { google() - jcenter() + maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'} } }