发生内存泄漏的原因
内存空间使用完毕后没有被回收,就会导致内存泄漏。虽然Java有垃圾回收机制,但是Java中任然存在很多造成内存泄漏的代码逻辑,垃圾回收器会回收掉大部分的内存空间,但是有一些内存空间还保持着引用,但是在逻辑上已经不会再用到的对象,这时候垃圾回收器就很无能为力,不能回收它们。
比如:
忘记释放分配的内存;
应用不需要这个对象了,但是却没有释放这个对象的引用;
强引用持有的对象,垃圾回收器是无法回收这个对象;
持有对象生命周期过长,导致无法回收;
Java判断无效对象的原理
图中的每个圆点都代表对象的内存资源,箭头代表可达路径。当圆点与GC Roots(Garbage Collection Roots)存在可达路径时,就表示当前资源正在被引用,虚拟机是无法对其进行回收的(如图中的黄色圆点),反过来,如果圆点与GC Roots不存在可达路径,则说明这个对象的内存空间不被引用,虚拟机可以将其回收掉。
Android(Java)平台的内存泄漏是指没用的对象资源与GC Roots之间保持可达路径,导致系统无法进行回收。
`
内存泄漏带来的危害
用户对单次的内存泄漏并没有什么感知,但是当泄漏积累到内存都被消耗完,就会导致卡顿,甚至崩溃。
内存泄漏是内存溢出OOM的重要原因之一,会导致Crash。
常见的可能发生内存泄漏的地方
1、最容易引发内存泄漏的问题是Context
例如Activity的Context,它包含大量的内存引用,一旦泄漏了Context,也就意味着泄漏它指向的所有对象。
造成Activity泄漏的常见原因:
Static Activities
在类中定义了static Activity变量,把正在运行的Activity实例赋值给这个静态变量。
当这个Activity生命周期结束后,但是这个静态变量却没有清空,就会导致内存泄漏。
因为static变量是贯穿这个应用的生命周期的,所以被泄露的Activity对象就会一直存在于应用的进程中,不会被垃圾回收器回收。
static Activity activity; //这种代码要避免
1
单例模式保存Activity
在单例模式中,Activity经常被用到,那么在内存中保存一个Activity实例是很实用的。但是由于单例的生命周期是这个应用的生命周期,那么会强制延长Activity的生命周期,是相当危险的,所以无论如何都不能在单例中保存类似Activity的对象。
public class Singleton {
private static Singleton instance;
private Context mContext;
private Singleton(Context context){
this.mContext = context;
}
public static Singleton getInstance(Context context){
if (instance == null){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton(context);
}
}
}
return instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在调用Singleton的getInstance()方法时传入了Activity,那么当instance没有释放的时,这个Activity会一直存在,导致内存泄漏。解决方法:
可以将new Singleton(context)改为new Singleton(context.getApplicationContext())即可,
这样便和传入的Activity没关系了。
1
2
Static View
同理,静态的View也是不建议的。
Inner Classes
内部类的优势是可以提高可读性和封装性,而且可以访问外部类,倒霉的是,导致内存泄漏的原因,就是内存类持有对外部类实例的强引用,例如在内部类中持有对Activity对象,解决方法:
1.将内部类变成静态内部类;
2.如果有强引用的Activity属性,则将该属性的引用方式改为弱引用;
3.在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务;
1
2
3
静态内部类不需要外部类的实例,可以通过外部类 . 静态内部类使用。
//这儿发生泄漏
public void test() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
解决办法:
//加上static,变成静态匿名内部类
public static void test() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
内部类引起内存泄漏的原因
https://blog.csdn.net/qq_22706515/article/details/51321718
5. Anonymous Classes
匿名类也维护了外部类的引用。当你在匿名类中执行耗时任务时,如果用户退出,会导致匿名类持有的Activity实例不会被垃圾回收器回收,直到异步任务结束。
6. Handler
handler中,Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列MessageQueue中,在Message消息没有被处理之前,Activity实例不会被销毁了,于是导致内存泄漏。解决办法:
1.可以把Handler类放在单独的类文件中,或者使用静态内部类便可以避免泄露;
2.如果想在Handler内部去调用所在的Activity,那么可以在handler内部使用弱引用的方式去指向所在Activity.使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的.
3.在界面销毁是,释放handler资源
@Override
protected void doOnDestroy() {
super.doOnDestroy();
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
mHandler = null;
mRenderCallback = null;
}
1
2
3
4
5
6
7
8
9
10
11
12
同样还有其他匿名类实例,如TimerTask、Threads等,执行耗时任务持有Activity的引用,都可能导致内存泄漏。
线程产生内存泄漏的主要原因在于线程的生命周期的不可控,如果我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,所以它引用的老的Activity也不会被销毁,因此就出现内存泄漏的问题。
要解决Activity的长期持有造成的内存泄漏,可以通过以下方法:
传入Application的Context,因为Application的生命周期就是整个应用的生命周期,所以没有任何问题。
如果此时传入的Activity的Context,当这个Context所对应的Activity退出时,主动结束执行的任务,释放Activity资源。
将线程的内部类改为静态内部类
因为非静态内部类会自动持有一个所属类的实例,如果所属类的实例已经结束生命周期,但内部类的方法仍在执行,就会hold其主体(引用),就使主体不能被释放,就造成内存泄漏,静态类编译后和非内部类是一样的,有自己的类名,不会引用所属类的实例,所以就不会造成泄漏。
如果需要引用Activity,使用弱引用
谨慎对context使用static关键字
2、Bitmap没有调用recycle()
Bitmap对象在不使用时,我们应该先调用recycle()释放内存,然后在设置为null
3、集合中对象没有清理造成的内存泄漏
我们经常把一些对象的引用加入到集合中,但我们不需要改对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static,那情况就更加严重了。解决方法:
在Activity退出之前,将集合里的东西clear,然后置为null,再退出程序。
1
4、注册没有取消造成的内存泄漏
这种Android内存泄漏比Java的内存泄漏还要严重,因为其他一些Android程序可能引用系统的Android程序的对象(如注册机制)。即使Android程序已经结束,但是别的应用程序仍然还有对Android程序的某个对象的引用,也会造成内存泄漏。解决方法:
1.使用ApplicationContext代替ActivityContext;
2.在Activity执行onDestory时,调用反注册;
1
2
5、资源对象没关闭造成的内存泄漏
资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。而不是等待GC来处理。
6、占用内存较多的对象(图片过大)造成内存泄漏
因为Bitmap占用的内存实在是太多了,特别是分辨率大的图片,如果要显示多张那问题就更显著了。Android分配给Bitmap的大小只有8M。解决方法:
等比例缩小图片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
1
2
对图片采用软引用,及时地进行recycle()操作
//软引用
SoftReference<Bitmap> bitmap = new SoftReference<Bitmap>(pBitmap);
//回收操作
if(bitmap != null) {
if(bitmap.get() != null && !bitmap.get().isRecycled()){
bitmap.get().recycle();
bitmap = null;
}
}
1
2
3
4
5
6
7
8
9
7、WebView内存泄漏
WebView造成内存泄漏的原因
解决方法:
@Override
protected void onDestroy() {
if( mWebView!=null) {
// 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
// destory()
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.stopLoading();
// 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearView();
mWebView.removeAllViews();
mWebView.destroy();
}
super.on Destroy();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
检测内存泄漏的方法
1. 使用 静态代码分析工具-Lint 检查内存泄漏
Lint 是 Android Studio 自带的工具, Analyze -> Inspect Code 然后选择想要扫面的区域即可
对可能引起泄漏的编码,Lint 都会进行温馨提示:
2. LeakCanary 工具
Square 公司出品的内存分析工具,官方地址如下:https://github.com/square/leakcanary/LeakCanary 需要在项目代码中集成。当内存泄漏发生时,LeakCanary 会弹窗提示并生成对应的堆存储信息记录。
3. Android Profiler分析器
打开Android Studio,编译代码,在模拟器或者真机上运行App,然后点击(如下),进入如下界面
点击MEMORY,进入
AndroidStudio3.0最新 Android Profiler分析器(cpu memory network 分析器)
如上图所示,内存分析器的默认视图包括以下内容:
① 强制执行垃圾收集事件的按钮。
② 捕获堆转储的按钮。
③ 记录内存分配的按钮。
④ 放大时间线的按钮。
⑤ 跳转到实时内存数据的按钮。
⑥ 事件时间线显示活动状态、用户输入事件和屏幕旋转事件。
⑦ 内存使用时间表,其中包括以下内容:
每个内存类别使用多少内存的堆栈图,如左边的y轴和顶部的颜色键所示。
虚线表示已分配对象的数量,如右侧y轴所示。
每个垃圾收集事件的图标。
————————————————
版权声明:本文为CSDN博主「哈哈云」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_31010739/article/details/88574481