Detecting memory leak translations using leakcanary
Original: https://academy.realm.io/cn/posts/droidcon-ricau-memory-leaks-leakcanary/
Github:https://github.com/square/leakcanary
Nov 18 2015
Directory
Chinese translation
Introduction (0:00)
Memory leaks: Non-technical commentary (1:40)
Leakcanary Rescue (3:47)
Technology explained memory leaks (8:06)
Analysis Heap (10:16)
Leakcanary save you from the Fire (12:04)
Leakcanary API Walkthrough (13:32)
What is a weak reference (14:17)
HAHA Memory Analyzer (16:55)
Implementation of Leakcanary (19:19)
Debug a real example (22:12)
Ignore SDK crashes (28:10)
The future of Leakcanary (29:14)
Q&a (31:50)
Chinese translation
Our App has experienced a lot of memory leaks causing OutOfMemoryError crashes, some even in production environments. Square's pierre-yvews Ricau developed in the LeakCanary end solves these problems. LeakCanaryis a tool to help you detect and repair memory leaks. In this sharing, Professor Pierre teaches you how to fix memory leak errors and make your App more stable and reliable.
Introduction (0:00)
Hi, I'm pierre-yvews Ricau (call me PY) and I'm working at Square now.
Square has an app called Square Register that helps you pay with your mobile device. When using this App, the user must first login to his personal account.
Unfortunately, there are times when the signature page crashes because of a memory overflow. To be honest, this is not the time to collapse-users and businesses are not sure whether the deal is complete, let alone when dealing with money. We are also acutely aware that we need to deal with a memory overflow or a memory leak.
Memory leaks: Non-technical commentary (1:40)
The memory leak solution I want to talk about is: Leakcanary.
LeakCanary 是一个可以帮助你发现和解决内存泄露的开源工具。
But what exactly is a memory leak? Let's start with a non-technical perspective, let's take an example.
...
An external reference points to an object that should not be pointed to. Small-scale memory leaks like this can cause big trouble after they accumulate.
Leakcanary Rescue (3:47)
That's why we're developing leakcanary.
I might have known now that the Android object that can be recycled should be destroyed in time.
But I still can't figure out whether these objects have been recycled. With the leakcanary, we:
给可被回收的 Android 对象上打了智能标记,智能标记能知道他们所指向的对象是否被成功释放掉。
If the object is still not released for a short period of time, he will take a snapshot of the memory. Leakcanary 随后 will publish the results:
帮助我们查看到内存到底怎么泄露了,并清晰的向我们展示那些无法被释放的对象的引用链。
Give a specific example: the signature page in our Square App. When the user prepares the signature, the App crashes because of a memory overflow error. We can't confirm where the memory error is.
The signature page holds a large Bitmap picture object with a user signature. The size of the picture is the same as the screen size of the user's phone-we suspect this could cause a memory leak. First, we can configure Bitmap as Alpha 8-bit to save memory. This is a very common fix, and the effect is good. But it does not solve the problem completely, but reduces the amount of leaked memory. But the memory leaks are still there.
The main problem is that our App is full, we should leave enough room for our signature pictures, but because a lot of memory leaks overlap and occupy a lot of memory.
Technology explained memory leaks (8:06)
Let's say I have an app, and this app can buy a baguette on the spot.
private static Button buyNowButton;
For some reason, I set this button static . The problem follows:
这个按钮除非你设置成了null,不然就内存泄露了!
You might say, "It's just a button, it's no big deal." The problem is that this button also has a member variable: called mContext , this thing points to one Acitvity , Acitivty again points Window to a, Window also has the entire View inheritance tree. It's a big memory space.
Static variables are one of the GC root types. The garbage collector tries to recycle all 非 GC root 的对象 , or some 被 GC root 持有的对象 . So if you create an object and remove all points from the object, he will be recycled. But once you set an object to GC root, he will not be recycled.
When you see a "stick button", it's obvious that the button holds a reference to the Activity, so we have to clean it up. When you're immersed in your code, it's hard to see the problem. You may only see the cited references. You can see that the activity refers to a Window, but who cites the activity?
You can IntelliJ do some analysis with a tool like this, but it doesn't tell you everything. Typically, you can organize the reference relationships of these objects into diagrams, but it is a one-way map.
Analysis Heap (10:16)
What can we do about it? Let's take a snapshot.
我们拿出所有的内存然后导出到文件里,这个文件会被用来分析和解析堆结构。其中一个工具叫做 Memory Analyzer,也叫 MAT。它会通过 dump 的内存,然后分析所有存活在内存中的对象和类。
You can use SQL to do some queries against him, similar to the following:
SELECT * FROM INSTANCEOF android.app.Activity a WHERE a.mDestroyed = true
This statement returns all instances of the state destroyed . Once you have discovered the leaked Activity, you can perform merge_shortest_paths the operation to come 计算出最短的 GC root 路径 . And find the object that prevents you from acitivty release.
The shortest path is said to be because there are many paths that can be reached from a GC root to acitivty. For example: My button parent view , also holds a Mcontext object.
When we see a memory leak, we usually don't need to look at all of these paths. We only need the shortest one. In that case, we ruled out the noise and quickly found the problem.
Leakcanary save you from the Fire (12:04)
It's a great thing to have a MAT that helps us find out about memory leaks. But in the context of a running App, it's hard to find the problem as our users discover leaks. We can't ask them to do the same thing again, then leave a message and send 70MB+ it back to us. We can do this in the background, but it's not Cool. We expect, 我们能够尽早的发现泄露 for example, to discover these problems when we develop them. This is also the meaning of the birth of Leakcanary.
An Activity has its own life cycle. You understand how it was created, how it was destroyed, and what you expect from him 在 onDestroy() 函数调用后,回收掉你所有的空闲内存 . If you have one 能够检测一个对象是否被正常的回收掉了的工具 , then you will be surprised to shout: "This could cause a memory leak!" It should have been recycled, but not garbage collected! ”
Activity is everywhere. Many people regard Activity as a divine Object, because it can operate Services, file systems, and so on. Often occurs when the object leaks, if the leaking object also holds the context object, then the context will be followed by leaks.
Resources resources = context.getResources();LayoutInflater inflater = LayoutInflater.from(context);File filesDir = context.getFilesDir();InputMethodManager inputMethodManager =(InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
Leakcanary API Walkthrough (13:32)
We look back at the smart tag smart pin and we want to know what happens when the end of life is over. Fortunately, Learkcanary has a very simple API.
First step: Create RefWatcher . The ref here is actually Reference the abbreviation. Passes an instance of an object to Refwatcher, which detects whether the object was successfully released.
RefWatcher refWatcher = LeakCanary.install(this);
Step Two: Monitor the Activity life cycle. Then, when OnDestroy is called, we pass in the Activity.
refWatcher.watch(this);// Make sure you don’t get installed twice.
What is a weak reference (14:17)
To find out how this works, I'll have to talk to you about weak references Weak References .
I've just mentioned that static domain variables hold references to activity. So the "order" button you just said will hold the Mcontext object, causing the Activity to not be released. This is called a strong reference Strong Reference .
一个对象可以有很多的强引用,在垃圾回收过程中,当这些强引用的个数总和为零的时候,垃圾回收器就会释放掉它。弱引用就是一种不增加引用总数的持有引用方式,垃圾回收期是否决定要回收一个对象,只取决于它是否还存在强引用。
So say, if we:
将我们的 Activity 持有为弱引用,一旦我们发现弱引用持有的对象已经被销毁了,那么这个 Activity 就已经被垃圾回收器回收了。否则,那可以大概确定这个 Activity 已经被泄露了。
The primary purpose of the weak reference is to do the Cache and is very useful. The main thing is to tell the GC that although I hold this object, the GC can be destroyed when it is needed, if there is no object in use.
In the following example, we inherit the WeakReference:
final class KeyedWeakReference extends WeakReference<Object> { public final String key; //唯一标识符 public final String name; KeyedWeakReference(Object referent, String key, String name, ReferenceQueue<Object> referenceQueue) { super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue")); this.key = checkNotNull(key, "key"); this.name = checkNotNull(name, "name"); }}
As you can see, we have added a key to the weak reference, and this key is a unique string. The idea is this: when we parse a heap dump file, we can iterate through all the KeyedWeakReference instances and find the corresponding Key.
First, we create a weakreference, and then we write "for a moment, I need to check for weak references". (though it may be a few seconds later). These things happen when we call the watch function.
public void watch(Object watchedReference, String referenceName) { checkNotNull(watchedReference, "watchedReference"); checkNotNull(referenceName, "referenceName"); if (debuggerControl.isDebuggerAttached()) return; final long watchStartNanoTime = System.nanoTime(); String key = UUID.randomUUID().toString(); retainedKeys.add(key); final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue); watchExecutor.execute(() -> ensureGone(reference, watchStartNanoTime));}
Behind all this, we call the System.GC (disclaimer-We should not have done it). However, this is kind of telling the garbage collector: "Hey, the garbage collector, is now a good time to clean up garbage." "And then we'll check again and if we find that some objects are still alive, then there may be a problem." We're going to trigger heap dump the operation.
HAHA Memory Analyzer (16:55)
heap dumpIt's a cool thing to do with your own hands. When I do this myself, it takes a lot of time and effort. I do the same things every time:
下载 heap dump 文件,在内存分析工具里打开它,找到实例,然后计算最短路径。
But I am very lazy, I do not want to do this again and again. (We're all lazy, right, because we're developers!) )
I could have written an eclipse plugin for the memory profiler, but the eclipse plugin mechanism was too bad. Later I brainwave that I could actually remove the UI from an Eclipse plugin and take advantage of its code.
HAHA is a 无 UI Android 内存分析器 . It's basically repackaging the code written by another person. At first, I just fork a different piece of code and then remove the UI part. Two years ago, someone re-forked my code and added Android support. It was two years before I found out about the man's storage, and then I repackaged it and uploaded it maven center .
I recently Android Studio implemented the code based on modifications. The code also said the past, will continue to maintain.
Implementation of Leakcanary (19:19)
We have our own library to parse the heap dump file, and it's easy to implement. We open heap dump , load it in, and then parse. Then we find our references based on key. Then we look at the references we have based on the existing Key. We get the instance, we get the object graph, and then we reverse-deduce the reference to the leak.
以上(下)所有的工作都发生在 Android 设备上。
- When Leakcanary detects that an Activity has been destroyed without being reclaimed by the garbage collector, it forces the export of a
heap dump file to exist on the disk.
- Then turn
另外一个进程 on to analyze the file to get the result of a memory leak. If you do this in the same process, you may have an out-of-memory problem when trying to parse the heap memory structure.
- Finally, you'll get a notification that the click will show
详细的内存泄漏链 . And it will show 内存泄漏的大小 that you will also be very clear about how much memory you can save after you've solved the memory leak.
Leakcanary is also a support API, so you 可以添加内存泄漏的回调 , for example, can 把内存泄漏问题传到服务器上 .
With the API, our program crash rate was reduced by 94%! It's a great stay!
Debug a real example (22:12)
This is a bug left over from Android's 4-year-old code modification, which was designed to fix another problem, but it brought an unavoidable memory leak. We don't know when it will be repaired.
Ignore SDK crashes (28:10)
Usually, there are always some memory leaks that you can't fix. We need to ignore these uncorrectable memory leak reminders at some point. In the leakcanary, there is a 内置的方法 problem to ignore that cannot be repaired.
I want to reiterate that leakcanary is just a development tool. Do not use it in a production environment. Once there is a memory leak, a notification is displayed to the user, which must not be what the user wants to see.
We still have a memory overflow error even with leakcanary. We still have multiple memory leaks. Is there any way to change that?
The future of Leakcanary (29:14)
public class OomExceptionHandler implements Thread.UncaughtExceptionHandler { private final Thread.UncaughtExceptionHandler defaultHandler; private final Context context; public OomExceptionHandler(Thread.UncaughtExceptionHandler defaultHandler, Context context) {...} @Override public void UncaughtException(Thread thread, Throwable ex) { if (containsOom(ex)) { File heapDumpFile = new File(context.getFilesDir(), "out-of-memory.hprof"); Debug.dumpHprofData(heapDumpFile.getAbsolutePath()); } defaultHandler.uncaughtException(thread, ex); } private boolean containsOom(Throwable ex) {...}}
This is a thread.uncaughtexceptionhandler, you can delegate a thread crash to it, it exports the heap dump file, and parses the memory leak in another process.
With this, we can do some fun things, such as listing all the Activity that should be destroyed but still surviving in memory, and then listing all the Detached View. We can then sort the leaked memory by importance.
I actually have a very simple Demo, which I wrote on the plane. Has not been released, because there are still some problems, the most serious problem is that there is not enough memory to parse the heap dump file. If you want to fix this problem, you have to think of another way. For example, using the stream method to load files and so on.
Q&a (31:50)
Q:leakcanary can be used in Kotlin development App?
PY: I don't know, but it should be, after all, they are byte-codes, and Kotlin have references.
Q: Do you always open leakcanary in Debug version? Or just the final version of the test.
PY: Different people have different methods, and we usually open them all the time.
Note: This article is a Chinese translation of the 2015 author's video content, and some of the content may have changed.
Detecting memory leak translations using leakcanary