什么是内存泄漏?

简单点说,就是指一个对象不再使用,本应该被回收,但由于某些原因导致对象无法回收,仍然占用着内存,这就是内存泄漏。

为什么会产生内存泄漏,内存泄漏会导致什么问题?

相比C++需要手动去管理对象的创建和回收,Java有着自己的一套垃圾回收机制,它能够自动回收内存,但是它往往会因为某些原因而变得“不靠谱”。

在Android开发中,一些不好的编码习惯就很可能会导致内存泄漏,而这些内存泄漏会导致应用内存越占越大,使得应用变得卡顿,甚至造成OOM(Out Of Memory)内存溢出问题,同时也使应用变得极其不稳定,因为当内存不足的时候,系统会优先回收那些“内存占比”大的应用。

Java的内存分配机制

首先我们先来了解下Java的内存分配机制,Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区。

静态存储区(方法区):主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。

栈区 :当方法被执行时,方法体内的局部变量(其中包括基础数据类型、对象的引用)都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

堆区 : 又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存,也就是对象的实例。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。

那什么样的对象会被回收呢?

Java内存管理有向图

为了更好理解 GC 的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从 main 进程开始执行,那么该图就是以 main 进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被 GC 回收。

常见的内存泄漏和解决方案

1、单例引起的内存泄漏

由于单例的静态特性导致它的生命周期和整个应用的生命周期一样长,如果有对象已经不再使用了,但又却被单例持有引用,那么就会导致这个对象就没办法被回收,从而导致内存泄漏。// 使用了单例模式

public class AppManager {

private static AppManager instance;

private Context context;

private AppManager(Context context) {

this.context = context;

}

public static AppManager getInstance(Context context) {

if (instance != null) {

instance = new AppManager(context);

}

return instance;

}

}

问题所在:

从上面的代码我们可以看出,在创建单例对象的时候,引入了一个Context上下文对象,如果我们把Activity注入进来,会导致这个Activity一直被单例对象持有引用,当这个Activity销毁的时候,对象也是没有办法被回收的。

解决方案:

在这里我们只需要让这个上下文对象指向应用的上下文即可(this.context=context.getApplicationContext()),因为应用的上下文对象的生命周期和整个应用一样长。

2、非静态内部类创建静态实例引起的内存泄漏

由于非静态内部类会默认持有外部类的引用,如果我们在外部类中去创建这个内部类对象,当频繁打开关闭Activity,会导致重复创建对象,造成资源的浪费,为了避免这个问题我们一般会把这个实例设置为静态,这样虽然解决了重复创建实例,但是会引发出另一个问题,就是静态成员变量它的生命周期是和应用的生命周期一样长的,然而这个静态成员变量又持有该Activity的引用,所以导致这个Activity销毁的时候,对象也是无法被回收的。public class MainActivity extends AppCompatActivity {

private static TestResource mResource = null;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

if(mResource == null){

mResource = new TestResource();

}

//...

}

class TestResource {

//...

}

}

问题所在:

其实这个和上面单例对象的内容泄漏问题是一样的,由于静态对象持有Activity的引用,导致Activity没办法被回收。

解决方案:

在这里我们只需要把非静态内部类改成静态内部类即可(static class TestResource)。

3、Handler引起的内存泄漏

记得我们刚学习Handler的时候,网上资料甚至学校教材“教科书”式的写法都是这样的Handler mHandler=new Handler(){

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

//to do something..

switch (msg.what){

case 0:

//to do something..

break;

}

}

};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

new Thread(new Runnable() {

@Override

public void run() {

//to do something..

mHandler.sendEmptyMessage(0);

}

}).start();

}

问题所在:

别看上面短短几行代码,其实涉及到了很多问题,首先我们知道程序启动时在主线程中会创建一个Looper对象,这个Looper里维护着一个MessageQueue消息队列,这个消息队列里会按时间顺序存放着Message,不清楚的朋友可以看下我之前写的这篇文章《从源码的角度彻底理解Android的消息处理机制》,然后上面的Handler是通过内部类来创建的,内部类会持有外部类的引用,也就是Handler持有Activity的引用,而消息队列中的消息target是指向Handler的,也就等同消息持有Handler的引用,也就是说当消息队列中的消息如果还没有处理完,这些未处理的消息(也可以理解成延迟操作)是持有Activity的引用的,此时如果关闭Activity,是没办法回收的,从而就会导致内存泄露。

解决方案:

和上文一样,我们需要先把非静态内部类改成静态内部类(如果是Runnable类也需要改成静态),然后在Activity的onDestroy中移除对应的消息,再来需要在Handler内部用弱引用持有Activity,因为让内部类不再持有外部类的引用时,程序也就不允许Handler操作Activity对象了。MyHandler myHandler = new MyHandler(this);

@Override

protected void onCreate(@Nullable Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

new Thread(new Runnable() {

@Override

public void run() {

myHandler.sendMessage(Message.obtain());

}

}).start();

}

@Override

protected void onDestroy() {

super.onDestroy();

//移除对应的Runnable或者是Message

//mHandler.removeCallbacks(runnable);

//mHandler.removeMessages(what);

mHandler.removeCallbacksAndMessages(null);

}

private static class MyHandler extends Handler {

private WeakReference mActivity;

public MyHandler(Activity activity) {

mActivity = new WeakReference(activity);

}

@Override

public void handleMessage(Message msg) {

if (mActivity.get() == null) {

return;

}

//to do something..

}

};

4、WebView引起的内存泄露

关于WebView的内存泄漏,这是个绝对的大大大大大坑!不同版本都存在着不同版本的问题,这里我只能给出我平时的处理方法,可能不同机型上存在的差异,只能靠积累了。

方法一:

首先不要在xml去定义,定义一个ViewGroup就行,然后动态在代码中new WebView(Context context)(传入的Context采取弱引用),再通过addView添加到ViewGroup中,最后在页面销毁执行onDestroy()的时候把WebView移除。

方法二:

简单粗暴,直接为WebView新开辟一个进程,在结束操作的时候直接System.exit(0)结束掉进程,这里需要注意进程间的通讯,可以采取Aidl,Messager,Content Provider,Broadcast等方式。

5、Asynctask引起的内存泄露

这部分和Handler比较像,其实也是因为内部类持有外部类引用,一样的改成静态内部类,然后在onDestory方法中取消任务即可。

6、资源对象未关闭引起的内存泄露

这块就比较简单了,比如我们经常使用的广播接收者,数据库的游标,多媒体,文档,套接字等。

7、其他一些

还有一些需要注意的,比如注册了EventBus没注销,添加Activity到栈中,销毁的时候没移除等。

好了,以上就是比较常见的内存泄露原因和对应的解决方案,当然还有一些其他的,这里没有办法一一阐述,还是需要大家平时不断去积累,总结,这里提供一个可以检查内存泄露的工具LeakCanary,只需要几行代码就可以轻松在应用内集成内存监控功能了。作者:李晨玮

链接:https://www.jianshu.com/p/b4f611d59ffc

java内部类内存泄漏,Android中常见的内存泄漏和解决方案相关推荐

  1. Android中常见的内存泄露

    内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏.内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会 ...

  2. android中常见的内存泄漏和解决的方法

    android中的内存溢出预计大多数人在写代码的时候都出现过,事实上突然认为工作一年和工作三年的差别是什么呢.事实上干的工作或许都一样,产品汪看到的结果也都一样,那差别就是速度和质量了. 写在前面的一 ...

  3. android中常见的内存泄漏和解决办法

    android中的内存溢出估计大多数人在写代码的时候都出现过,其实突然觉得工作一年和工作三年的区别是什么呢,其实干的工作也许都一样,产品汪看到的结果也都一样,那区别就是速度和质量了. 写在前面的一点儿 ...

  4. android中内存泄露,Android中的内存泄露

    编辑推荐: 本文来自于csdn,本文主要从java的内存模型讲起,最终举出几个内存泄露的例子和解决方案. java运行时内存模型 具体信息:http://gityuan.com/2016/01/09/ ...

  5. Android开发中常见的内存泄露案例以及解决方法总结

    Android开发中常见的内存泄露案例以及解决方法总结 参考文章: (1)Android开发中常见的内存泄露案例以及解决方法总结 (2)https://www.cnblogs.com/shen-hua ...

  6. Java I/O在Android中应用(一)

    Java I/O在Android中应用(一) 前言(废话) 本来想周末拉一拉进度的,结果跑完10KM马拉松之后,发现自己已经完全没有力气再去做任何事情了. 讲一些日常的事情吧,最近家里人说要给我介绍对 ...

  7. C程序中常见的内存操作错误

    对C/C++程序员来说,管理和使用虚拟存储器可能是个困难的, 容易出错的任务.与存储器有关的错误属于那些令人惊恐的错误, 因为它们在时间和空间上, 经常是在距错误源一段距离之后才表现出来. 将错误的数 ...

  8. Java开发人员在编程中常见的雷!

    身为一名Java从业人员,其职场生涯就是一边踩"坑",一边上升的过程.这个过程中不仅要学会修改无数bug,也要学会越过很多"坑".今天,千锋老师为大家分享一些J ...

  9. 一些Java开发人员在编程中常见的雷!

    身为一名Java从业人员,其职场生涯就是一边踩"坑",一边上升的过程.这个过程中不仅要学会修改无数bug,也要学会越过很多"坑".今天,小千为大家分享一些Jav ...

最新文章

  1. 硅谷产品实战-总结:23、增长的核心在于减少用户阻力
  2. 不是语言之争---Go vs Erlang
  3. C#使用HTTP头检测网络资源是否有效
  4. Python练习 | Python之图像的基本操作和处理
  5. linuxliveu盘怎么用_U盘数据如何恢复?U盘打不开怎么办?
  6. Git - 回滚到指定版本
  7. Be the Winner(结论:反nim博弈)
  8. 在d3中使用2D.js获取图形间的交点
  9. NET的JIRA活动时间线REST API
  10. J2EE的核心API与组件
  11. 7.PL_SQL——在PL_SQL程序中内嵌查询语句、DML语句、事物处理语句和游标属性
  12. 极化码理论及算法研究3-Arikan原版论文学习总结
  13. 运放搭建电压电流转换电路分析
  14. 案例|工业物联网解决方案•生产数据可视化
  15. Vue中的Ajax(26th)
  16. 11.3-11.4kmp专题训练
  17. 关于笔记本连接显示器检测不到的问题(NoVideoInput)
  18. cdr 表格自动填充文字_当文字内容太多excel单元格盛不下应该怎么做
  19. 机器学习实例--预测美国人口收入状况
  20. grep和egrep的区别

热门文章

  1. 5分钟商学院-个人篇-高效能人士的素养
  2. 向量空间模型(VSM)与TF-IDF
  3. 为何4个字节int取值范围是-2^31 到2^31 - 1
  4. 瑞友天翼应用虚拟化系统RCE漏洞复现+利用
  5. ddms java 截图,DDMS 那些事
  6. 自定义 ViewGroup 的时候,关于 LayoutParams 有哪些注意事项?
  7. silabs ZLL
  8. 【 ViSP(1) - Linux Melodic 源码安装 ViSP】
  9. java中的比较详解
  10. 团队项目(百药食坊)---总结