内存泄露是如何产生的?java
当一个对象已经不须要再使用了,本该被回收时,而有另一个正在使用的对象持有它的引用从而致使它不能被回收,这致使本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。程序员
内存泄露是形成OOM的主要缘由之一。windows
内存泄露的对象是什么?数组
内存分配有三种策略:静态(静态存储区/方法区)、栈、堆。app
静态存储区(方法区):内存在程序编译的时候就已经分配好,这块内存在程序整个运行期间都存在。它主要存放静态数据、全局static数据和常量。异步
栈区:在执行函数时,函数内局部变量的存储单元均可以在栈上建立,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,可是分配的内存容量有限。ide
堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员本身负责在适当的时候用free或delete释放内存(Java则依赖垃圾回收器)。动态内存的生存期能够由咱们决定,若是咱们不释放内存,程序将在最后才释放掉动态内存。函数
堆和栈的区别:工具
栈:定义一些基本数据类型变量和应用变量。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的做用域后,java会自动释放掉为该变量分配的内存空间,该内存空间能够马上被另做他用。oop
堆:用于存放全部由new建立的对象(内容包括该对象其中的全部成员变量)和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还能够在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,之后就能够在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量至关于为数组或者对象起的一个别名,或者代号。
堆是不连续的内存区域(由于系统是用链表来存储空闲内存地址,天然不是连续的),堆大小受限于计算机系统中有效的虚拟内存(32bit系统理论上是4G),因此堆的空间比较灵活,比较大。栈是一块连续的内存区域,大小是操做系统预约好的,windows下栈大小是2M(也有是1M,在编译时肯定,VC中可设置)。
对于堆,频繁的new/delete会形成大量内存碎片,使程序效率下降。对于栈,它是先进后出的队列,进出一一对应,不产生碎片,运行效率稳定高。
结论:
局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。
——由于它们属于方法中的变量,生命周期随方法而结束。
成员变量所有存储与堆中(包括基本数据类型,引用和引用的对象实体)
——由于它们属于类,类对象终究是要被new出来使用的。
内存为何会泄露?
Java的内存管理就是对象的分配和释放问题。在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不须要经过调用函数来释放内存,但它只能回收无用而且再也不被其它对象引用的那些对象所占用的空间。
堆内存中的长生命周期的对象持有短生命周期对象的强/软引用,尽管短生命周期对象已经再也不须要,可是由于长生命周期对象持有它的引用而致使不能被回收,这就是Java中内存泄露的根本缘由。
Java内存回收机制:
从程序的主要运行对象(如静态对象/寄存器/栈上指向的堆内存对象等)开始检查引用链,当遍历一遍后获得上述这些没法回收的对象和他们所引用的对象链,组成没法回收的对象集合,而其余孤立对象(集)就做为垃圾回收。GC为了可以正确释放对象,必须监控每个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都须要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象再也不被引用。
关于“引用”:通俗的讲,经过A能调用并访问到B,那就说明A持有B的引用,或A就是B的引用。
Java中对引用的分类:
在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大并且声明周期较长的对象时候,能够尽可能应用软引用和弱引用技术。
软/弱引用能够和一个引用队列(ReferenceQueue)联合使用,若是软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列能够得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。
常见的内存泄露状况:
一、集合类泄露
集合类若是仅仅有添加元素的方法,而没有相应的删除机制,致使内存被占用。若是这个集合类是全局性的变量 (好比类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,极可能致使集合所占用的内存只增不减。
二、非静态内部类建立静态实例形成内存泄漏。
修复方法:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,若是须要使用Context,推荐的使用Application 的 Context。
匿名内部类有可能会持有当前Activity实例,若是将这个引用传入到异步线程,此线程和Activity生命周期不一致是,将会形成内存泄露。
三、Handler 形成的内存泄漏
Handler 发送的 Message 还没有被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有,且Handler的声明周期和Activity的不一致,容易致使内存没法被正确释放。
修复方法:在Activity中避免使用非静态内部类,即将Handler改成static,此时Handler的存活周期将于Activity无关,同事以弱应用的方式引入Activity,避免直接将Activity做为context传入。每次使用前需判空。
Looper 线程的消息队列中仍是可能会有待处理的消息,因此咱们在 Activity 的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。
public class MainActivity extends AppCompatActivity { private MyHandler mHandler = new MyHandler(this); private TextView mTextView ; private static class MyHandler extends Handler { private WeakReference reference; public MyHandler(Context context) { reference = new WeakReference<>(context); } @Override public void handleMessage(Message msg) { MainActivity activity = (MainActivity) reference.get(); if(activity != null){ activity.mTextView.setText(""); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView)findViewById(R.id.textview); loadData(); } private void loadData() { //...request Message message = Message.obtain(); mHandler.sendMessage(message); } @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); } }
四、尽可能避免使用 static 成员变量
若是成员变量被声明为 static,那咱们都知道其生命周期将与整个app进程生命周期同样。将会致使app常驻内存,从而致使app频繁重启,形成耗电、耗流量。
修复方法:不要在类初始时初始化静态成员。能够考虑lazy初始化。
五、避免 override finalize()
finalize 方法被执行的时间不肯定,finalize 方法只会被执行一次,即便对象被复活,若是已经执行过了 finalize 方法,再次被 GC 时也不会再执行了,含有Finalize方法的object须要至少通过两轮GC才有可能被释放。
六、线程泄露
线程也是形成内存泄露的一个重要的源头。线程产生内存泄露的主要缘由在于线程生命周期的不可控。好比线程是 Activity 的内部类,则线程对象中保存了 Activity 的一个引用,当线程的 run 函数耗时较长没有结束时,线程对象是不会被销毁的,所以它所引用的老的 Activity 也不会被销毁,所以就出现了内存泄露的问题。
六、资源未关闭形成的内存泄漏
应在Activity被销毁时及时关闭或注销BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源。
七、在 Java 的实现过程当中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,好比使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁建立谁释放的原则。
八、单例形成的内存泄露
因为单例的静态特性使得单例的生命周期和应用的生命周期同样长,若是一个对象已经不须要使用了,而单例对象还持有该对象的引用,则致使这个对象不能被正常回收,形成内存泄露。
解决办法:
public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context.getApplicationContext();// 使用Application 的context } public static AppManager getInstance(Context context) { if (instance != null) { instance = new AppManager(context); } return instance; } }
或者如下写法,连Context都不用传进来了
... context = getApplicationContext(); ... /** * 获取全局的context * @return 返回全局context对象 */ public static Context getContext(){ return context; } public class AppManager { private static AppManager instance; private Context context; private AppManager() { this.context = MyApplication.getContext();// 使用Application 的context } public static AppManager getInstance() { if (instance != null) { instance = new AppManager(); } return instance; } }
检测程序中内存泄露的工具:LeakCanary、MAT等
一、对于生命周期比Activity长的对象若是须要应该使用ApplicationContext
二、对于须要在静态内部类中使用非静态外部成员变量(如:Context、View ),能够在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏
三、对于再也不须要使用的对象,显示的将其赋值为null,好比使用完Bitmap后先调用recycle(),再赋为null
四、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期
五、对于生命周期比Activity长的内部类对象,而且内部类中使用了外部类的成员变量,能够这样作避免内存泄漏:
(1)将内部类改成静态内部类
(2)静态内部类中使用弱引用来引用外部类的成员变量
六、在涉及到Context时先考虑ApplicationContext,固然它并非万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景以下:
参考文献:
http://dev.qq.com/topic/59152c9029d8be2a14b64dae