android 内存泄漏全面解析

引言:

C/C++ 自己去分配内存和释放内存--手动管理

malloc free

什么是内存泄露:内存不在GC掌控之内了。

当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而就导致

对象不能被回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏

了解java的GC内存回收机制:某对象不再有任何的引用的时候才会进行回收。

ArrayList list = new Arraylist();

了解内存分配的几种策略:

1.静态的

静态的存储区:内存在程序编译的时候就已经分配好,这块的内存在程序整个运行期间都一直存在。

它主要存放静态数据、全局的static数据和一些常量。

2.栈式的

在执行函数(方法)时,函数一些内部变量的存储都可以放在栈上面创建,函数执行结束的时候这些存储单元就会自动被释放掉。

栈内存包括分配的运算速度很快,因为内置在处理器的里面的。当然容量有限。

3.堆式的

也叫做动态内存分配。有时候可以用malloc或者new来申请分配一个内存。在C/C++可能需要自己负责释放(java里面直接依赖GC机制)。

在C/C++这里是可以自己掌控内存的,需要有很高的素养来解决内存的问题。java在这一块貌似程序员没有很好的方法自己去解决垃圾内存,需要的是编程的时候就要注意自己良好的编程习惯。

区别:堆是不连续的内存区域,堆空间比较灵活也特别大。

栈式一块连续的内存区域,大小是有操作系统觉决定的。

堆管理很麻烦,频繁地new/remove会造成大量的内存碎片,这样就会慢慢导致效率低下。

对于栈的话,他先进后出,进出完全不会产生碎片,运行效率高且稳定。

public class Main{

int a = 1;

Student s = new Student();

public void XXX(){

int b = 1;//栈里面

Student s2 = new Student();
}
}

1.成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体)---因为他们属于类,类对象最终还是要被new出来的。

2.局部变量的基本数据类型和引用存储于栈当中,引用的对象实体存储在堆中。-----因为他们属于方法当中的变量,生命周期会随着方法一起结束。

我们所讨论内存泄露,主要讨论堆内存,他存放的就是引用指向的对象实体。

有时候确实会有一种情况:当需要的时候可以访问,当不需要的时候可以被回收也可以被暂时保存以备重复使用。

比如:ListView或者GridView、REcyclerView加载大量数据或者图片的时候,

图片非常占用内存,一定要管理好内存,不然很容易内存溢出。

滑出去的图片就回收,节省内存。看ListView的源码----回收对象,还会重用ConvertView。

如果用户反复滑动或者下面还有同样的图片,就会造成多次重复IO(很耗时),

那么需要缓存---平衡好内存大小和IO,算法和一些特殊的java类。

算法:lrucache(最近最少使用先回收)

特殊的java类:

利于回收,StrongReference,SoftReference,WeakReference,PhatomReference

StrongReference强引用:

回收时机:从不回收 使用:对象的一般保存 生命周期:JVM停止的时候才会终止

SoftReference,软引用

回收时机:当内存不足的时候;使用:SoftReference结合ReferenceQueue构造有效期短;生命周期:内存不足时终止

WeakReference,弱引用

回收时机:在垃圾回收的时候;使用:同软引用; 生命周期:GC后终止

PhatomReference 虚引用

回收时机:在垃圾回收的时候;使用:合ReferenceQueue来跟踪对象呗垃圾回收期回收的活动; 生命周期:GC后终止

开发时,为了防止内存溢出,处理一些比较占用内存大并且生命周期长的对象的时候,可以尽量使用软引用和弱引用。

软引用比LRU算法更加任性,回收量是比较大的,你无法控制回收哪些对象。

比如使用场景:默认头像、默认图标。

ListView或者GridView、REcyclerView要使用内存缓存+外部缓存(SD卡)

常见内存泄露

1.单例模式导致内存对象无法释放而导致内存泄露

能用Application的context就用Application的

2.设置监听很容易出现内存泄露
例如:

handler.post(callback)
onDestroy(){
    handler.removeCallback();
}

3.非静态内部类引起内存泄露

public void loadData(){//隐士持有MainActivity实例。MainActivity.this.a
        new Thread(new Runnable() {
            @Override
            public void run() {
            while(true){
                try {
                //int b=a;
                Thread.sleep(1000);
                } catch (InterruptedException e) {
                e.printStackTrace();
                }
            }
            }
        }).start();
        }

解决方案:
将非静态内部类修改为静态内部类。
(静态内部类不会隐士持有外部类)

4.资源未关闭引起的内存泄露情况

比如:BroadCastReceiver、Cursor、Bitmap、IO流、自定义属性attribute
attr.recycle()回收。
当不需要使用的时候,要记得及时释放资源。否则就会内存泄露。

5.无限循环动画
没有在onDestroy中停止动画,否则Activity就会变成泄露对象。
比如:轮播图效果。

如何找到项目中存在的内存泄露的这些地方呢

1.确定是否存在内存泄露
1)Android Monitors的内存分析
最直观的看内存增长情况,知道该动作是否发生内存泄露。
动作发生之前:GC完后内存1.4M; 动作发生之后:GC完后内存1.6M

2)使用MAT内存分析工具
MAT分析heap的总内存占用大小来初步判断是否存在泄露
Heap视图中有一个Type叫做data object,即数据对象,也就是我们的程序中大量存在的类类型的对象。
在data object一行中有一列是“Total Size”,其值就是当前进程中所有Java数据对象的内存总量,
一般情况下,这个值的大小决定了是否会有内存泄漏。
我们反复执行某一个操作并同时执行GC排除可以回收掉的内存,注意观察data object的Total Size值,
正常情况下Total Size值都会稳定在一个有限的范围内,也就是说由于程序中的的代码良好,没有造成对象不被垃圾回收的情况。
反之如果代码中存在没有释放对象引用的情况,随着操作次数的增多Total Size的值会越来越大。
那么这里就已经初步判断这个操作导致了内存泄露的情况。

2.先找怀疑对象(哪些对象属于泄露的)
MAT对比操作前后的hprof来定位内存泄露是泄露了什么数据对象。(这样做可以排除一些对象,不用后面去查看所有被引用的对象是否是嫌疑)
快速定位到操作前后所持有的对象哪些是增加了(GC后还是比之前多出来的对象就可能是泄露对象嫌疑犯)
技巧:Histogram中还可以对对象进行Group,比如选择Group By Package更方便查看自己Package中的对象信息。

  1. MAT分析hprof来定位内存泄露的原因所在。(哪个对象持有了上面怀疑出来的发生泄露的对象)
    1)Dump出内存泄露“当时”的内存镜像hprof,分析怀疑泄露的类;
    2)把上面2得出的这些嫌疑犯一个一个排查个遍。步骤:
    (1)进入Histogram,过滤出某一个嫌疑对象类
    (2)然后分析持有此类对象引用的外部对象(在该类上面点击右键List Objects--->with incoming references)
    (3)再过滤掉一些弱引用、软引用、虚引用,因为它们迟早可以被GC干掉不属于内存泄露
    (在类上面点击右键Merge Shortest Paths to GC Roots--->exclude all phantom/weak/soft etc.references)
    (4)逐个分析每个对象的GC路径是否正常
    此时就要进入代码分析此时这个对象的引用持有是否合理,这就要考经验和体力了!
    (比如上课的例子中:旋转屏幕后MainActivity有两个,肯定MainActivity发生泄露了,
    那谁导致他泄露的呢?原来是我们的CommonUtils类持有了旋转之前的那个MainActivity他,
    那是否合理?结合逻辑判断当然不合理,由此找到内存泄露根源是CommonUtils类持有了该MainActivity实例造成的。
    怎么解决?罪魁祸首找到了,怎么解决应该不难了,不同情况解决办法不一样,要靠你的智慧了。)

context.getapplictioncontext()可以吗?
可以!!只要让CommonUtils类不直接只有MainActivity的实例就可以了。

一般我是最笨的方法解决
new出来对象,用完后把它 = null;这样算不算优化
假如:方法里面定义的对象,要去管吗?一般不需要管。
自己=null,要自己去控制所有对象的生命周期 判断各种空指针,有点麻烦。
但是在很多时候去想到主动将对象置为null是很好的习惯。

判断一个应用里面内存泄露避免得很好,怎么看?
当app退出的时候,这个进程里面所有的对象应该就都被回收了,尤其是很容易被泄露的(View,Activity)是否还内存当中。
可以让app退出以后,查看系统该进程里面的所有的View、Activity对象是否为0.
工具:使用AndroidStudio--AndroidMonitor--System Information--Memory Usage查看Objects里面的views和Activity的数量是否为0.

常用优化工具

1.Profiler

2.LeakCanary

你可能感兴趣的:(android 内存泄漏全面解析)