From a13c6ff541a6fa672dab94c38cddb3859ff6406b Mon Sep 17 00:00:00 2001 From: LeLe673893702 <673893702@qq.com> Date: Thu, 27 Dec 2018 00:36:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9E=81=E5=AE=A2=E6=97=B6=E9=97=B4=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=EF=BC=9A13055537258?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这份作业主体前几天就完成了,年底事情很多,一直没有整理,今天花点时间整理了一下。 原理:使用HaHa库进行hprof解析,Leak-Canary中的ShortestPathFinder获取堆栈信息,过程: 1.通过findClass获取到ClassObj。 2.如果按照正常遍历ClassObj的heap会发现一共有4块heap分别是default、app、image、zygote,而我们项目中的主要来源自appHeap中,所以我只提取了这个Heap中的Instance列表。 3.然后遍历Instance列表,通过HaHaHelper的fieldValue提取出buffer数组。 4.拿到buffer数组我先对数组进行大小分类,如果大小不同可以大概率判定为是不同图片,过滤掉一部分。 5.剩下一部分的通过进行遍历比较value值,判断是否是相同图片。后续看能不能使用分治算法进行比较。 6.通过结果通过对象转成json字符串打印。 心得和体会: 1.其中遇到的问题和其他同学一样就是无法打印出堆栈信息,后来通过查看研究Leak-Canary源码发现了ShortestPathFinder这个类,并对此进行了部分删减。 2.之前用过一段时间的MAT,感觉有点难用,这份作业我给了新的自动化分析的思路,很感谢。 3.最后是执行力上的思考,作业虽然大概完成了,但是一直没花时间整理,给自己敲了一个警钟,以后一定要把一件事情有始有终的做完。 --- DuplicatedBitmapAnalyzer/build.gradle | 34 +-- DuplicatedBitmapAnalyzer/libs/classes.jar | Bin 0 -> 28314 bytes .../src/com/hprof/bitmap/Analyzer.java | 133 +++++++++ .../src/com/hprof/bitmap/DuplicateResult.java | 65 +++++ .../src/com/hprof/bitmap/HahaHelper.java | 197 +++++++++++++ .../src/com/hprof/bitmap/ImageUtil.java | 24 ++ .../src/com/hprof/bitmap/LeakNode.java | 33 +++ .../src/com/hprof/bitmap/Main.java | 6 +- .../com/hprof/bitmap/ShortestPathFinder.java | 264 ++++++++++++++++++ build.gradle | 4 +- 10 files changed, 726 insertions(+), 34 deletions(-) create mode 100644 DuplicatedBitmapAnalyzer/libs/classes.jar create mode 100644 DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/Analyzer.java create mode 100644 DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/DuplicateResult.java create mode 100644 DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/HahaHelper.java create mode 100644 DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/ImageUtil.java create mode 100644 DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/LeakNode.java create mode 100644 DuplicatedBitmapAnalyzer/src/com/hprof/bitmap/ShortestPathFinder.java 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 0000000000000000000000000000000000000000..e60ee0e525a73a3bc54eccf516037bec48527acc GIT binary patch literal 28314 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

`A#eUd!8$9$Y%oD*kJ z-BZ89-w&bHVhNl7C!zsS^|+ zhD}Cq8_`XXJOawmOpDj@je44E6)oc&nzj;RXM9GhVE?`{JtjZoR|pk0KwiPk7TW6e z6Di@;i#=&3{;AD|&ung^|37T2(MRLtThSpPimCsW;$ZyCX4yX(U~4l|RWoxlCo{W0 zCWFiV)0Ks>tn3TsS72Gc)=ZU8siYxdU|>vg4ErZ#(TzPpP$>)u%lbjCV z2Fi&P-Z8y&K~Fx4c|4!{QH%FZI-fh}>F(-vyLIy*S@br6lQxU_OtLKY*jaMtIln=+ z6{)ojw@xWrK40z_Gk|Y%clolxswq-hZf4b-nz)BHPTw(m)Qmc)^*Xp*2Tv;knYFS3=G-$q zv0AKXV+B`xsYyN#roG1XqGW5dYdyo0s6MPY-#W>oSZ*v`Y9*UvbVYM1TD(J60`>wkcKIN5HioDseh@3)hatt4h-&L`0&(5NxPou@YL>eZj;ER3XS zH&4coTbfF}MiNtNNa;yL!eO!g!l&bf^n6Q5^^D^}G0%KQF^}Mf_YCNPrrPnI@$|iY2gepbQ@KILU90@Qqhm>u!}HL za2op4MD+FG#WBW4lW@J#eyK#MF+9a!b3=E2BWsLDl5T_rWsOm@EHrv|NdIi~*6bVBE4^&;4!or3l;vJQVnY0P$D?1nxllnM{o2In z<@5i#{}J?m^gsHi@6rDx{A&145Al*A{I8ebOKRaa!oTbh^M5h_6Y;BcIQMS|Uxa^w z_^XfnA8@}K9rOK$OME$je?6lAAMRIo@;?xNwb0FYZ47?P&D}#IFJ^+TV^Kf$V>W_!p<>S4|M*ZvbJ* zKLh@!>gx}`{?}|e)o;NWrT-rMH<8gFqW_xHWBo0 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/'} } }