Debugging Memory Leaks on Android (for Beginners)

来源:互联网 发布:window.open php 编辑:程序博客网 时间:2024/05/16 19:22

使用Debug.dumpHprofData 分析内存泄漏

原文链接:https://www.novoda.com/blog/debugging-memory-leaks-on-android-for-beginners-programmatic-heap-dumping-part-1/

This is the first post in a series about debugging Android memory issues—subsequent posts will deal with using Eclipse Memory Analyzer to inspect heap dumps, as well as looking at common Android-specific memory consumption pitfalls. We’ll start with Programmatic Heap Dumping.

The contents of this post ought to be useful to developers new to the Android platform (regardless of Java experience) as it details Android-specific APIs and the use of Android SDK utilities to successfully interoperate with the standard JVM toolchain (e.g. hprof-conv which converts Dalvik heap dumps into the J2SE HPROF format, expected by Eclipse Memory Analyzer; MAT). Once this has been accomplished, the process of debugging leaks is more or less identical for Android applications and standard Java code running on a traditional JVM.

What’s a Heap?

All we really need to know about the heap for the purposes of this post is that it’s a slab of VM-managed memory into which (most) Java objects are allocated—certainly all of the objects we’re likely to care about. When we’re talking about the heap size of a particular object—how much of the heap it occupies—we talk of Shallow Size (or Shallow Heap) and Retained Size (or Retained Heap). The shallow size is the amount required for an object itself, while the retained size is the amount required for an object and all of the objects it refers to (i.e. via instance attributes).

A heap dump is a portable (i.e. file-based) representation of a VM’s heap at a particular point in time.

Obtaining Heap Dumps On Demand

Interactively getting an Android process to dump its heap is trivial with DDMS (there’s a Dump HPROF File button). That’s not our concern; we’re focused on obtaining dumps at particular points in a program’s execution by using the android.os.Debug API, which is an approach we might want to take if pathological memory consumption is peaking at points which aren’t easily identified through interactive use.

That said, our example is fairly contrived—an in-app Button which triggers a heap dump is isomorphic to the Dump HPROF File DDMS feature—figuring out when to trigger the dumping is entirely application-specific—the code samples are to illustrate how to do it, using a triggering mechanism likely to be familiar to anybody who’s written an interactive Android application.

Here’s an example of a View.OnClickListener which accepts a String data directory, and writes a heap dump within it, when the onClick method is invoked:

package com.novoda.example.MemoryLeaker;

import android.os.Debug;
import android.view.View;

import java.io.File;
import java.io.IOException;

public class HeapDumpOnClickListener implements View.OnClickListener {
private static final String HPROF_DUMP_BASENAME = “MemoryLeaker.dalvik-hprof”;
private final String dataDir;

public HeapDumpOnClickListener(String dataDir) {    this.dataDir = dataDir;}@Overridepublic void onClick(View v) {    String absPath = new File(dataDir, HPROF_DUMP_BASENAME).getAbsolutePath();    try {        // this'll cause a collection        Debug.dumpHprofData(absPath);    } catch (IOException e) {        e.printStackTrace();    }}

}
view rawHeapDumpOnClickListener.java hosted with ❤ by GitHub
Below is an excerpt of an Activity which attaches the above listener to a Button, and passes in a sensible value for the path prefix, so that the dump ends up somewhere useful (note that this method will overwrite an existing file at the supplied path):

package com.novoda.example.MemoryLeaker;

import android.app.Activity;
import android.os.Bundle;

public class LeakingActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

    findViewById(R.id.btn_heap).setOnClickListener(        new HeapDumpOnClickListener(getApplicationInfo().dataDir));}...

}
view rawLeakingActivity.java hosted with ❤ by GitHub
When the Dump Heap button is activated, we’ll be able to retrieve the file from the Android device/emulator by executing the following command on the host machine (assuming ANDROIDHOME/platformtoolsisinPATH):

% adb pull /data/data/com.novoda.example.MemoryLeaker/MemoryLeaker.dalvik-hprof
226 KB/s (9160365 bytes in 17.973s)
If we plan to do anything with the heap dump, we’ll need to convert it to the J2SE format, as outlined above, using the hprof-conv binary, in ANDROIDHOME/tools(whichoughttobeinPATH):

% hprof-conv MemoryLeaker.dalvik-hprof MemoryLeaker.hprof
% ls -lh MemoryLeaker.hprof
-rw-r–r– 1 moe staff 9.2M 18 Apr 15:33 MemoryLeaker.hprof
Grabbing a Heap Dump When Things Go Wrong

Alternatively, if we’re dealing with a worst-case scenario and have absolutely no idea what’s causing the leakage, or lack the patience to babysit the application until it falls apart, we can install a handler which’ll catch an OutOfMemoryError and try to write a snapshot of heap use at that point. Alternatives would be using either Activity.onLowMemory or Application.onLowMemory, which have vaguely defined semantics, and are not as definitively catastrophic as an uncaught OOM (from Application’s documentation: “this is called when the overall system is running low on memory, and would like actively running process to try to tighten their belt. While the exact point at which this will be called is not defined…”).

Back to Uncaught exception handlers: these are per-Thread, which is clear from the entrypoint—Thread’s setUncaughtExceptionHandler instance method. Below is an example of a Thread.UncaughtExceptionHandler suitable for passing in:

package com.novoda.example.MemoryLeaker;

import android.os.Debug;

import java.io.File;
import java.io.IOException;

public class HeapDumpingUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private static final String HPROF_DUMP_BASENAME = “LeakingApp.dalvik-hprof”;
private final String dataDir;

public HeapDumpingUncaughtExceptionHandler(String dataDir) {    this.dataDir = dataDir;}@Overridepublic void uncaughtException(Thread thread, Throwable ex) {    String absPath = new File(dataDir, HPROF_DUMP_BASENAME).getAbsolutePath();    if(ex.getClass().equals(OutOfMemoryError.class)) {        try {            Debug.dumpHprofData(absPath);        } catch (IOException e) {            e.printStackTrace();        }    }    ex.printStackTrace();}

}
view rawHeapDumpingUncaughtExceptionHandler.java hosted with ❤ by GitHub
And the code to set this up at Application startup:

package com.novoda.example.MemoryLeaker;

import android.app.Application;

public class MemoryLeakingApplication extends Application {
@Override
public void onCreate() {
super.onCreate();

    Thread.currentThread().setUncaughtExceptionHandler(            new HeapDumpingUncaughtExceptionHandler(getApplicationInfo().dataDir));}

}
view rawMemoryLeakingApplication.java hosted with ❤ by GitHub
Next Steps

The next post in this series will detail how to use MAT to find likely culprits in a heap dump which has been fetched and converted using the above method.

0 0
原创粉丝点击