Android内存泄漏分析和解决方法

目录

一、内存分配

了解java内存的分配原理对分析内存有非常大帮助,特别是静态内存部分啦。

Java和c++的内存分配情况好比有一堵内存动态分配和垃圾收集技术围成的“高墙”,墙外面的人想进去(c++),墙里面的人想出来(java)。

c++是对象实例没有指针指向它,被孤立,导致内存泄露,java是没用的对象实例还有多余的引用,GC不能回收。

这里写图片描述
JVM内存运行图

这里写图片描述

共享区域
方法区( Method Area):存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译编译后的代码。
堆(Heap):存放java几乎所有对象实例 ,JIT除外。如果对象需要的内存不够,OutOfMemoryError异常。
隔离区域
虚拟机栈(VM Stack):方法执行的内存模型,用于存储方法入口、局部变量表、操作数栈、动态连接。如果栈深度大于虚拟机的栈深,抛出StackOverflowError异常,如果对象实例对象需要的内存不够,抛出OutOfMemoryError异常。
本地方法栈(Native Method Stack): 和VM Static 差不多的作用。只是虚拟机是native方法的内存模型(c/c++方法)。
程序计数器(Program Counter Register):存放线程的行号指示器,每个线程都有自己的独立的计数器。

android虚拟机
这里写图片描述

这里写图片描述

二、GG过程

长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。

这里写图片描述
对于不同的语言平台来说,进行标记回收内存的算法是不一样的,像 Android(Java)则采用 GC-Root 的标记回收算法(Mark sweap)。下面这张图就展示了 Android 内存的回收管理策略(图来自Google 2011的IO大会)

图中的每个圆节点代表对象的内存资源,箭头代表可达路径。当圆节点与 GC Roots 存在可达路径时,表示当前资源正被引用,虚拟机是无法对其进行回收的(如图中的黄色节点)。反过来,如果圆节点与 GC Roots 不存在可达路径,则意味着这块对象的内存资源不再被程序引用,系统虚拟机可以在 GC 过程中将其回收掉。

深入JVM虚拟机(三) Java GC垃圾收集 https://blog.csdn.net/yuan_xw/article/details/51434007

三、GC算法

1.引用计数法
这里写图片描述
2、标记清除法

这里写图片描述

4、复制算法
5、分代思想:

四、常见内存泄漏原因

1.非静态内部类的静态实例

public class SecondActivity extends AppCompatActivity {
    private static Object inner;
    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.bt_next);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                createInnerClass();
                finish();
            }
        });
    }

    void createInnerClass() {
        class InnerClass {
        }
        inner = new InnerClass();//1
    }
}

不要使用

2.匿名内部类的静态实例

public class AsyncTaskActivity extends AppCompatActivity {
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_task);
        button = (Button) findViewById(R.id.bt_next);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startAsyncTask();
                finish();
            }
        });
    }
    void startAsyncTask() {
        new AsyncTask<Void, Void, Void>() {//1
            @Override
            protected Void doInBackground(Void... params) {
                while (true) ;
            }
        }.execute();
    }
}

public class AsyncTaskActivity extends AppCompatActivity {
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_task);
        button = (Button) findViewById(R.id.bt_next);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startAsyncTask();
                finish();
            }
        });
    }
    void startAsyncTask() {
        new MyAsyncTask().execute();
    }
    private static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
        @Override
        protected Void doInBackground(Void... params) {
            while (true) ;
        }
    }
}

application service activity 的调用地方
这里写图片描述
3 Handler内存泄漏
Handler的Message被存储在MessageQueue中,有些Message并不能马上被处理,它们在MessageQueue中存在的时间会很长,这就会导致Handler无法被回收。如果Handler 是非静态的,则Handler也会导致引用它的Activity或者Service不能被回收。

public class HandlerActivity extends AppCompatActivity {
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        button = (Button) findViewById(R.id.bt_next);
        final Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mHandler.sendMessageDelayed(Message.obtain(), 60000);
                finish();
            }
        });
    }
}

解决方法就是要使用一个静态的Handler内部类,Handler持有的对象要使用弱引用,并且在Activity的Destroy方法中移除MessageQueue中的消息

public class HandlerActivity extends AppCompatActivity {
    private Button button;
    private MyHandler myHandler = new MyHandler(this);
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        button = (Button) findViewById(R.id.bt_next);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myHandler.sendMessageDelayed(Message.obtain(), 60000);
                finish();
            }
        });
    }
    public void show() {

    }
    private static class MyHandler extends Handler {
        private final WeakReference<HandlerActivity> mActivity;

        public MyHandler(HandlerActivity activity) {
            mActivity = new WeakReference<HandlerActivity2>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            if (mActivity != null && mActivity.get() == null) {
                mActivity.get().show();
            }
        }
    }
    @Override
    public void onDestroy() {
        if (myHandler != null) {
            myHandler.removeCallbacksAndMessages(null);
        }
        super.onDestroy();
    }
}

3.各种监听没有释放
自己添加的监听记得及时去除监听

4.单例类持有外部的引用 Activity Context

public class AppSettings { 
 private Context mAppContext;
 private static AppSettings mAppSettings = new AppSettings();
 public static AppSettings getInstance() {
  return mAppSettings;
 }

 public final void setup(Context context) {
  mAppContext = context;
 }
}

对于不是必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们可以考虑使用Application Context来代替Activity的Context,这样可以避免Activity泄露,

public final void setup(Context context) {
 mAppContext = context.getApplicationContext(); 
}

5 静态View

使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致Activity无法被回收,解决的办法就是在onDestory方法中将静态View置为null。

6 WebView

通常的解决办法就是为WebView单开一个进程,使用AIDL与应用的主进程进行通信。WebView进程可以根据业务需求,在合适的时机进行销毁

7 资源对象未关闭

资源对象比如Cursor、File等,往往都用了缓冲,不使用的时候应该关闭它们。把他们的引用置为null,而不关闭它们,往往会造成内存泄漏。因此,在资源对象不使用时,一定要确保它已经关闭,通常在finally语句中关闭,防止出现异常时,资源未被释放的问题。

8 集合中对象没清理

通常把一些对象的引用加入到了集合中,当不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就会更加严重。

9 Bitmap对象

临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。
避免静态变量持有比较大的bitmap对象或者其他大的数据对象,如果已经持有,要尽快置空该静态变量。

http://liuwangshu.cn/application/performance/ram-3-memory-leak.html

五、内存泄漏的的分析

1.使用 adb shell dumpsys meminfo [PackageName],可以打印出指定包名的应用内存信息。它是最简单直接反应是否哟内存泄漏的工具。

主要查看
total memory:一般退出activity都已有内存的变化,回收;
java heap:java对象的分配情况;
Activities:当前存活的activity,如果有多个activity,当心有内存泄漏!


localhost:jdk7u-dev li$ adb shell dumpsys meminfo cn.xxxx.android
Applications Memory Usage (in Kilobytes):
Uptime: 188198513 Realtime: 458787859

** MEMINFO in pid 26780 [cn.xxxx.android] **
                   Pss  Private  Private  SwapPss     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap    16970    16776      156        0    32128    14411    17716
  Dalvik Heap    42663    38076     4448        0    55728    39344    16384
 Dalvik Other     8273     8264        0        0                           
        Stack     1816     1816        0        0                           
       Ashmem      238      120        0        0                           
      Gfx dev    21244    21244        0        0                           
    Other dev       70        0       68        0                           
     .so mmap     8305      460     5232       98                           
    .jar mmap        4        0        0        0                           
    .apk mmap    34883    22316    12196        0                           
    .ttf mmap      603        0      188        0                           
    .dex mmap     7153     6260      772        0                           
    .oat mmap     5801        0      648        0                           
    .art mmap     4331     2096      972        3                           
   Other mmap      766        4      684        1                           
   EGL mtrack    11152    11152        0        0                           
    GL mtrack     5576     5576        0        0                           
      Unknown     7891     7888        0        6                           
        TOTAL   177847   142048    25364      108    87856    53755    34100

 App Summary
                       Pss(KB)
                        ------
           Java Heap:    41144
         Native Heap:    16776
                Code:    48072
               Stack:     1816
            Graphics:    37972
       Private Other:    21632
              System:    10435

               TOTAL:   177847       TOTAL SWAP PSS:      108

 Objects
               Views:      224         ViewRootImpl:        4
         AppContexts:        5           Activities:        1
              Assets:       18        AssetManagers:        3
       Local Binders:      100        Proxy Binders:       38
       Parcel memory:       24         Parcel count:       96
    Death Recipients:        4      OpenSSL Sockets:        0
            WebViews:        0

 SQL
         MEMORY_USED:      369
  PAGECACHE_OVERFLOW:      105          MALLOC_SIZE:       62

 DATABASES
      pgsz     dbsz   Lookaside(b)          cache  Dbname
         4       24             61        11/40/7  /data/user/0/cn.xxxx.android/databases/babyfs
         1        3                         0/0/0    (attached) temp
         4       24             72      591/143/8  /data/user/0/cn.xxxx.android/databases/statistic
         1        3                         0/0/0    (attached) temp
         4       20             48        12/30/9  /data/user/0/cn.xxxx.android/databases/tls_sdk.db

 Asset Allocations
    zip:/data/app/cn.xxxx.android-1/base.apk:/assets/ca.crt: 1K

2.使用 adb shell dumpsys activity [PackageName],可以打印出指定包名的应用acitivity信息

3.Android studio profiler
利用堆转储查看当前没有被回收的activity引用情况,对可疑的引用进行修改。解决activity不能回收问题。

Heap类型分为:
App Heap – 当前App使用的Heap
Image Heap – 磁盘上当前App的内存映射拷贝
Zygote Heap – Zygote进程Heap(每个App进程都是从Zygote孵化出来的, 这部分基本是framework中的通用的类的Heap)
Class List View – 类列表方式
Package Tree View – 根据包结构的树状显示
紧接着下面的表名:
列名 说明
Class Name Heap中的所有Class
Total Count 内存中该类这个对象总共的数量
Heap Count 堆内存中这个类对象的个数
Sizeof 每个该实例占用的内存大小
Shallow Size 所有该类的实例占用的内存大小
Retained Size 所有该类对象被释放掉,会释放多少内存
接下来是B区域
列名 说明
Instance 该类的实例
Depth 深度, 从任一GC Root点到该实例的最短跳数
Dominating Size 该实例可支配的内存大小
C区域则描述的是B中实例具体被引用信息。

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值