Android 内存分析指北

android 内存泄漏分析指北

简单来说内存泄漏就是当对象不再被应用程序使用,但是垃圾回收器却不能移除它们,因为它们正在被引用

java 垃圾回收介绍:

Java 虚拟机运行所管理的内存包括以下几个运行时的数据区域

如下图:

程序计数器: 一块比较小的内存区域,可以看作是当前线程所执行的字节码的行号指示器。且每个线程都有一个独立的程序计数器。

java 虚拟机栈: 线程私有的,描述的是java 方法执行的内存模型,每个线程执行的时候都会创建一个栈帧用于储存 局部变量、操作数栈、动态链接、方法出口、等信息。一个方法的调用到执行结束的过程就对应一个栈帧在虚拟机栈中的入栈到处栈的过程。 虚拟机局部变量表中存放了编译可知的各种局部数据类型(boolean、byte、char、short、int 、float 、long、double)、对象引用、返回地址 。

本地方法栈:和虚拟机栈类似,其中虚拟机栈为执行java 方法服务,而本地方法栈为虚拟机使用的native 的方法服务java 堆:java 虚拟机所管理的内存中最大的一块,且其是被所有的线程共享的一块内存区域,在虚拟机启动的时候创建。该区域的唯一目的就是来存放对象实例的。 java 堆是垃圾啊回收管理的主要区域,因此在很多的时候被叫做"GC堆"

方法区:和java 堆一样是各个线程共享的内存区域,用来存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码和数据。

运行中常量池:为方法区的一部分。class 文件中除了有类的版本、字段、方法、接口等描述信息外、还有一项信息是常量池、用来存放编译期生产的各种字面量和符号引用。

GC 时那些内存需要释放:

首先对于程序计数器、虚拟机栈、本地方法栈 这3个区域都是随线程而生、所线程而亡。 栈中的栈帧随着方法的进入和退出执行着如栈和出栈的操作。每一个栈帧中分配的内存基本上在类结构确定下来的时候已经是可知的了。所以在这几个区域就不需要考虑内存回收的问题,因为在方法结束,或者线程结束的时候内存自然就会回收了。

但是java 堆和方法区确不一样,如一个接口中的多个实现类需要的内存可能不一样,一个方法中的的多个分支需要的内存也可能不一样,我们只有在程序处于运行的情况下才可以知道会创建那些对象,这部分的内存的分配和回收也是动态的,垃圾回收所关注的也这一部分的内容。

确定哪些对象是存活的,那些已经“死去”(即不可能被任何途径使用的对象)

方式:

A、引用计数法: 给对象添加一个引用计数器,每当有一个地方引用它时,计数器就加1 ; 当引用失效时就减1;任何时刻计数器为0 的对象是就不可能再被使用的。 (主流的JAVA 虚拟器并没有使用引用计数法来管理内存,其中主要的原因是它很难解决对象的相互循环引用的问题) 。

B、可达性分析法: 通过一系列的称为GC Roots 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Rotts 没有任何引用链 相连时(即GC Roots 到这个对象不可达),则证明这个对象是不可用的。

如下图论:

如上图所示,object1~object4对GC Root都是可达的,说明不可被回收,object5和object6对GC Root节点不可达,说明其可以被回收

可作为GC roots 的对象包括:虚拟机栈(栈帧中的本地变量表)中引用的对象(就是指正执行的方法中的局部变量,参数,临时值所引用的对象)、方法区中的静态属性引用的对象、方法区中常量引用的对象、本地方法中JNI 引用的对象。

各中引用:

强引用:只要强引用还存在,垃圾收集器将永远不会回收掉被引用的对象。

软引用:用来描述一些还有用但并非必须的对象、在系统将要发生内存溢出异常之前,将会把这些对象列进对象回收范围中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出的异常。

弱引用:也是用来描述非必须对象,其强度比软引用更弱些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

虚引用:一种最弱的引用关系、一个对象是否有引用存在完全不会影响其生存时间、且无法通过虚引用活得一个对象的实例。一个虚引用唯一的目的就是在这个对象在被收集器回收的时候能够收到一个系统通知。

参考:深入理解java 虚拟机第二版

内存泄漏检测:

1、Dump Appmeminfo:在一个App 中存在着很多个Activity ,其实我们只需要一个一个界面 去检查其是否存在Activity泄漏的情况 ,

利用adb shell dumpsys meminfo <package name>dumpapp 的内存信息,该信息中有包含当前 app 中所未释放的 activity 的数量,以及view 的个数,如下图所示, 在进入一个界面之前检查下activity 数量和view 的数量 ,在退出该界面后检查下在查看一边activityview 的数量,对比进入和退出后activity 和view 的数量是否有差异。

如下图:目前存在着1 个activity 184 个 view 。 因此我们如果要检查某个activity 是否存在泄漏,我们只需要在进入该activity 之前dump 下该信息, 然后在进入该activity ,进行一系列操作,然后点击back键,退出 。

ps:dump meminfo 信息的时候,需要等内存稳定下然后进行dump, 或者通过手动gc(利用 androidmonitor 工具手动gc 按钮)后进行dump 操作。

2、跑自动化测试脚本,或者跑Monkey 查看内存的增长曲线(测试检测的方式)。

3、LeakCanary Square 公司开源作品,使用方便,可以直接定位到泄漏的对象,并且给出调用链。

内存泄漏分析,相关工具使用:

分析内存泄漏,第一步得复现问题,然后抓取hprof 文件。

1、androidStudio Hprof 分析Activity 泄漏:利用Android studioMonitors 的工具抓取 点击Start Allocation Tracking 抓取hprof 文件。 具体怎样使用该工具 可参考如下链接。

hporf 分析

例如如下分析结果,存在 HandleActivity 存在泄漏。 然后直接参考上面的链接,找出对应引起泄漏的点。 下图是HandlerActivity的内部类释放不了造成Activity 泄漏的。

2、 MAT 使用:MAT工具打开hprof 文件时,需要先利用hprof-conv 工具将hprof 文件转换下, 利用如下命令。

hprof-conv demo.hprof demo_conv.hprof

打开hprof文件如下:搜索关键字可以搜索出相关对象对象的个数,所占的内存(如下图)。

右键Merge shortest paths to gc roots 选择 exclude all phantom/weak/soft etc references 可以定位到gc root 这样就可以确定时哪个对象没有释放导致HandlerActiivty 不能释放 。

对比两个hprof 文件,查看某个对象的个数对比如下图:

通过上图就可以看出HandlerActivity 没有被释放。

常见内存泄漏类型以及解决方案:

静态引用造成的内存泄漏
private static DeviceUtil stance;![Alt text](./mat_2.png)

private Context context;
....
public static DeviceUtil getStance(Context context) {
    if(stance == null){
        synchronized (DeviceUtil.class){
            stance = new DeviceUtil(context);
        }
    }
    return stance;
}

private DeviceUtil(Context context){
    this.context = context;
}

调用的地方:

private void showVersionCode(){
    final int versionCode = DeviceUtil.getStance(HandlerActivity.this)
            .getVersionCode(getPackageName());
    Toast.makeText(HandlerActivity.this,
            "versionCode="+versionCode,Toast.LENGTH_LONG).show();
}

在上面的例子中DeviceUtil 持有了Activity从而导致其释放不了。

解决方案:Activity context 修改成Application context . 因为Applicaition 时全局的,生命周期时和app 生命周期一样的。

非静态内部类造成内存泄漏:
public class HandlerActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_handler);
    startThread();
}

class InnerThread extends Thread{
    @Override
    public void run() {
        super.run();
        int index = 0;
        while (index < Integer.MAX_VALUE){
            index ++;
            try {
                sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

private void startThread(){
    InnerThread thread = new InnerThread();
    thread.start();
}

如上面代码中InnerThread 会隐式 的持有外部类的引用,因为在这里InnerThread 线程的生命周期超过了Activity 的生命周期,当finish 当前ActivityInnerThread 并不会停止且持有了当前Activity 从而导致HandleActivity 泄漏。

解决方案: 将内部类修改为静态内部类的方式 。 如下:

static class InnerThread extends Thread{
    @Override
    public void run() {
        super.run();
        int index = 0;
        while (index < Integer.MAX_VALUE){
            index ++;
            try {
                sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
匿名内部类造成的内存泄漏
public class HandlerActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_handler);
    //startThread();
    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            final int what = msg.what;
            if(what == 0){
                Toast.makeText(HandlerActivity.this,"receive message",Toast.LENGTH_LONG).show();
            }
            super.handleMessage(msg);
        }
    };

    Message message = handler.obtainMessage(0);
    handler.sendMessageDelayed(message,1000*10);
}

在这里Message 会在主线程中存在10s , 且Message 会持有handler 对象,而handler 会隐式持有Activity ,从而导致Activity 泄漏。

修改方案: 将匿名内部类修改成静态内部类即可,参考上面的例子。

不需要的监听未移除导致的内存溢出:

常见的如:广播监听没有被移除,所以需要注册和解除注册成对出现 。

Native 泄漏:

在android 中Native 泄漏都大部分是通过jni 调用Native 方法,所以只需要检查Java 端调用JNI 的地方即可。

binder 泄漏:
资源对象未关闭 :

资源性对象如Cursor、File、Socket,应该在使用后及时关闭。未在finally中关闭,会导致异常情况下资源对象未被释放的隐患。

总结:一般来说,内存泄漏都是因为泄漏对象的引用被传递到该对象的范围之外,或者说内存泄漏是因为持有对象的长期引用,导致对象无法被 GC 回收。为了避免这种情况,我们可以选择在对象生命周期结束的时候,解除绑定,将引用置为空,或者使用弱引用。

原文地址:https://www.cnblogs.com/scoftlin/p/10099539.html

时间: 2024-08-10 11:30:21

Android 内存分析指北的相关文章

Android内存分析工具

Android的一些内存知识 垃圾回收(GC) 垃圾回收包含两个过程: 判定阶段,也就是判断哪些对象可以被回收, 收集阶段,是指具体的回收策略. 判定阶段主要有两种方式 引用计数,对象每多一个引用计数加1,少一个引用计数减1,计数为0时就表示这个对象可以被回收了.但是引用计数有个缺点,不能判断循环应用的情况,所以就有了下面的方式 根搜索,从一些根对象(GCRoot)开始遍历搜索,如果一个对象无法被搜索到,说明这个对象可以被回收了. 可以作为GCRoot的对象: 1 一些虚拟机栈中的对象:2 方法

Android内存分析和调优(上)

Android内存分析和调优(上) Android内存分析工具(四):adb命令

Android内存分析工具DDMS heap + MAT 安装和使用

一  Java内存分析工具扫盲 如果像我一样一点都不了解,可以先进行内存分析工具扫盲 MAT介绍:     Eclipse Memory Analyzer(MAT)一个功能丰富的 JAVA 堆转储文件分析工具,可以用于发现内存漏洞和减少内存消耗. 二  Eclipse MAT插件安装 当前机器环境描述: [plain] view plaincopy 系统: Ubuntu 12.04 LTS 64 Eclipse for Mobile Developers Version: Juno Servic

Android 内存分析

MAT使用入门 字数4552 阅读746 评论0 喜欢12 本人博客地址:http://androidperformance.com本文博客地址:http://androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT/本文微博地址:http://weibo.com/270099576 MAT简介 MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速.功能丰富的JAVA heap分析工具

Android内存分析之MAT

面试中经常会问到内存优化,我们在开发过程中也多少会遇到OOM的问题,根据大牛们的博客,记录下我的学习思路 一.为何会OOM? 1. 一直以来Andorid手机的内存都比iPhone(iPhone6RAM1G)大的多,Android却经常出现OOM,这是为何? 这个是因为Android系统对dalvik的vm heapsize 作了硬性限制,当java进程申请的java空间超过阀值时,就会抛出OOM异常(这个阀值可以是48M.24M.16M等,视机型而定),可以通过adb shell getpro

[Android Studio] 2019年Android Studio配置指北

Android Studio是我学习Android开发路上的第一块绊脚石,新建一个项目,一行代码没动,直接编译不起来,本文为什么叫指北,因为我太难了 本文讲解在9102年如何在国内网络不通畅的情况下流畅的使用Android Studio 1. 关闭代理 网上能搜索出一堆通过增加国内镜像站的代理来使下载SDK的速度飞快,然而我一个都用不了,建议不要使用代理 2. 改hosts dl.google.com的DNS无法解析,这是导致很多问题的元凶,通过手动在hosts中增加域名和ip的映射来解决这个问

Android 内存分析工具 - LogCat GC

一.GC_Reason 触发垃圾回收的回收的集中原因: 类型 描述 GC_CONCURRENT 内存使用将满时,并发的进行垃圾回收. GC_FOR_MALLOC 当内存已满应用尝试分配内存时会出触发垃圾回收,所以系统会停止应用进行垃圾整理 GC_HPROF_DUMP_HEAP 当创建HPROF文件分析内存时触发垃圾收集. GC_EXPLICIT 显示的垃圾收集,例如当你调用gc() (应该避免调用,而是交由系统处理) GC_EXTERNAL_ALLOC 只会在API 10以下版本触发.新版都只会

[高级]Android 内存分析工具MAT

前提条件: 1,电脑安装了java 运行环境 2,手机端开启了 USB 调试开关 3,获取 root 权限 基本步骤: 1,使用eclipse 自带的 DDMS 工具分析各线程的内存使用情况,如下图所示 Heap视图界面会定时刷新,在对应用的不断的操作过程中就可以看到内存使用的变化. 怎样判断当前进程是否有内存泄漏呢? 这里需要注意一个值:VM Heap页面中部有一个data object选项,即数据对象,也就是我们的程序中大量存在的类类型的对象. 在data object一行中有一列是“Tot

android内存分析、加载本地图片内存优化

从网上学习了MAT插件来查看内存使用情况,分析之后发现手上的应用对本地图片这边的内存损耗很大,查了相关资料之后发现,如果采用setImageBitmap.setImageResource这些来加载本地资源,会产生较大的损耗.因为这些方法在完成 decode 后,最终都是通过 Java 层的 createBitmap 来完成的,需要消耗更多内存.因此,改用先通过 BitmapFactory.decodeStream 方法,创建出一个 bitmap,再将其设为 ImageView 的 source,