有没有一种方法让Android进程在OutOfMemoryError时生成堆转储?

21

太阳JVM支持-XX:+HeapDumpOnOutOfMemoryError选项,在Java进程用尽堆时转储堆。

在Android上是否有类似的选项,可以在OutOfMemoryException发生时使Android应用程序转储堆?手动使用DDMS进行正确定时操作可能很困难。

3个回答

28

进一步扩展CommonsWare的答案:

我不知道这是否有效,但你可以尝试添加顶层异常处理程序,并在其中如果是OutOfMemoryError ,请求堆转储

我使用以下代码成功地遵循了他的建议,在我的Android应用程序中实现了它:

public class MyActivity extends Activity {
    public static class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread thread, Throwable ex) {
            Log.e("UncaughtException", "Got an uncaught exception: "+ex.toString());
            if(ex.getClass().equals(OutOfMemoryError.class))
            {
                try {
                    android.os.Debug.dumpHprofData("/sdcard/dump.hprof");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            ex.printStackTrace();
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Thread.currentThread().setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
    }
}

创建完dump文件后,需要将其从手机复制到电脑上:在手机上点击“打开USB存储”,找到文件并将其复制到硬盘上。

然后,如果您想使用Eclipse Memory Analyzer(MAT)分析该文件,则需要转换文件:hprof-conv.exe dump.hprof dump-conv.hprof(hprof-conv位于android-sdk/tools下)

最后,使用MAT打开dump-conv.hprof文件。


我注意到这通常有效,但有时不会导致崩溃。不确定为什么。 - emmby
我也做了同样的事情,有时候转储文件会损坏(或者至少hprof-conv认为是这样)。我只想建议在转储之前执行System.gc()。 - snowdragon
1
很可能是在文件写入之前应用程序被终止了,或者更糟的是,在文件写入完成之前被终止了。 - Andrew Alcock
可能是一个鸡生蛋或蛋生鸡的问题,你的内存已经用完了,所以无法正确创建转储。然而,也有可能是因为分配失败导致的失败,当它无法分配请求的数量时,它有足够的内存来创建一个转储。 - auselen

11

11
这是一个改进版本,除了原始实现外,此实现还支持以下功能:
  • 在所有线程(而不仅仅是主线程)中捕获内存不足错误
  • 即使内存不足错误隐藏在其他错误中,也能识别出来。在某些情况下,内存不足错误被封装在运行时错误中。
  • 也可以调用原始默认的未捕获异常处理程序。
  • 仅适用于DEBUG版本。
用法:在onCreate方法中的应用程序类中调用静态initialize方法。
package test;
import java.io.File;
import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;

import android.os.Environment;
import android.util.Log;

import com.example.test1.BuildConfig;

public class OutOfMemoryDumper implements Thread.UncaughtExceptionHandler {

    private static final String TAG = "OutOfMemoryDumper";
    private static final String FILE_PREFIX = "OOM-";
    private static final OutOfMemoryDumper instance = new OutOfMemoryDumper();

    private UncaughtExceptionHandler oldHandler;

    /**
     * Call this method to initialize the OutOfMemoryDumper when your
     * application is first launched.
     */
    public static void initialize() {

        // Only works in DEBUG builds
        if (BuildConfig.DEBUG) {
            instance.setup();
        }
    }

    /**
     * Keep the constructor private to ensure we only have one instance
     */
    private OutOfMemoryDumper() {
    }

    private void setup() {

        // Checking if the dumper isn't already the default handler
        if (!(Thread.getDefaultUncaughtExceptionHandler() instanceof OutOfMemoryDumper)) {

            // Keep the old default handler as we are going to use it later
            oldHandler = Thread.getDefaultUncaughtExceptionHandler();

            // Redirect uncaught exceptions to this class
            Thread.setDefaultUncaughtExceptionHandler(this);
        }
        Log.v(TAG, "OutOfMemoryDumper is ready");
    }

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {

        Log.e(TAG, "Uncaught exception: " + ex);
        Log.e(TAG, "Caused by: " + ex.getCause());

        // Checking if the exception or the original cause for the exception is
        // an out of memory error
        if (ex.getClass().equals(OutOfMemoryError.class)
                || (ex.getCause() != null && ex.getCause().getClass()
                        .equals(OutOfMemoryError.class))) {

            // Checking if the external storage is mounted and available
            if (isExternalStorageWritable()) {
                try {

                    // Building the path to the new file
                    File f = Environment
                            .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);

                    long time = System.currentTimeMillis();

                    String dumpPath = f.getAbsolutePath() + "/" + FILE_PREFIX
                            + time + ".hprof";

                    Log.i(TAG, "Dumping hprof data to: " + dumpPath);

                    android.os.Debug.dumpHprofData(dumpPath);

                } catch (IOException ioException) {
                    Log.e(TAG,"Failed to dump hprof data. " + ioException.toString());
                    ioException.printStackTrace();
                }
            }
        }

        // Invoking the original default exception handler (if exists)
        if (oldHandler != null) {
            Log.v(TAG, "Invoking the original uncaught exception handler");
            oldHandler.uncaughtException(thread, ex);
        }
    }

    /**
     * Checks if external storage is available for read and write
     * 
     * @return true if the external storage is available
     */
    private boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        Log.w(TAG,"The external storage isn't available. hprof data won't be dumped! (state=" + state + ")");
        return false;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接