Android内存泄露解析和优化
来源:互联网 发布:大白菜安装linux iso 编辑:程序博客网 时间:2024/06/07 01:32
前言
本文主要阐述内存泄露的原因和常见处理的方式;
JAVA是垃圾回收语言的一种,开发者无需特意管理内存分配。但是JAVA中还是存在着许多内存泄露的可能性,如果不好好处理内存泄露,会导致APP内存单元无法释放被浪费掉,最终导致内存全部占据堆栈(heap)挤爆进而程序崩溃。
内存泄露:
说到内存泄露,就不得不提到内存溢出,这两个比较容易混淆的概念,我们来分析一下。
内存泄露:程序在向系统申请分配内存空间后(new),在使用完毕后未释放。结果导致一直占据该内存单元,我们和程序都无法再使用该内存单元,直到程序结束,这是内存泄露。
内存溢出:程序向系统申请的内存空间超出了系统能给的。比如内存只能分配一个int类型,我却要塞给他一个long类型,系统就出现oom。又比如一车最多能坐5个人,你却非要塞下10个,车就挤爆了。
大量的内存泄露会导致内存溢出(oom)。
内存:
想要了解内存泄露,对内存的了解必不可少。
JAVA是在JVM所虚拟出的内存环境中运行的,JVM的内存可分为三个区:堆(heap)、栈(stack)和方法区(method)。
栈(stack):是简单的数据结构,但在计算机中使用广泛。栈最显著的特征是:LIFO(Last In, First Out, 后进先出)。比如我们往箱子里面放衣服,先放入的在最下方,只有拿出后来放入的才能拿到下方的衣服。栈中只存放基本类型和对象的引用(不是对象)。
堆(heap):堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身。
方法区(method):又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
内存的概念大概理解清楚后,要考虑的问题来了:
到底是哪里的内存会让我们造成内存泄露?
内存泄露原因分析:
在JAVA中JVM的栈记录了方法的调用,每个线程拥有一个栈。在线程的运行过程当中,执行到一个新的方法调用,就在栈中增加一个内存单元,即帧(frame)。在frame中,保存有该方法调用的参数、局部变量和返回地址。然而JAVA中的局部变量只能是基本类型变量(int),或者对象的引用。所以在栈中只存放基本类型变量和对象的引用。引用的对象保存在堆中。
当某方法运行结束时,该方法对应的frame将会从栈中删除,frame中所有局部变量和参数所占有的空间也随之释放。线程回到原方法继续执行,当所有的栈都清空的时候,程序也就随之运行结束。
而对于堆内存,堆存放着普通变量。在JAVA中堆内存不会随着方法的结束而清空,所以在方法中定义了局部变量,在方法结束后变量依然存活在堆中。
综上所述,栈(stack)可以自行清除不用的内存空间。但是如果我们不停的创建新对象,堆(heap)的内存空间就会被消耗尽。所以JAVA引入了垃圾回收(garbage collection,简称GC)去处理堆内存的回收,但如果对象一直被引用无法被回收,造成内存的浪费,无法再被使用。所以对象无法被GC回收就是造成内存泄露的原因!
垃圾回收机制
垃圾回收(garbage collection,简称GC)可以自动清空堆中不再使用的对象。在JAVA中对象是通过引用使用的。如果再没有引用指向该对象,那么该对象就无从处理或调用该对象,这样的对象称为不可到达(unreachable)。垃圾回收用于释放不可到达的对象所占据的内存。
实现思想:我们将栈定义为root,遍历栈中所有的对象的引用,再遍历一遍堆中的对象。因为栈中的对象的引用执行完毕就删除,所以我们就可以通过栈中的对象的引用,查找到堆中没有被指向的对象,这些对象即为不可到达对象,对其进行垃圾回收。
如果持有对象的强引用,垃圾回收器是无法在内存中回收这个对象。
引用类型:
在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(reachable)状态,程序才能使用它。从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
1. 强引用(Strong reference)
实际编码中最常见的一种引用类型。常见形式如:A a = new A();等。强引用本身存储在栈内存中,其存储指向对内存中对象的地址。一般情况下,当对内存中的对象不再有任何强引用指向它时,垃圾回收机器开始考虑可能要对此内存进行的垃圾回收。如当进行编码:a = null,此时,刚刚在堆中分配地址并新建的a对象没有其他的任何引用,当系统进行垃圾回收时,堆内存将被垃圾回收。
2. 软引用(Soft Reference)
软引用的一般使用形式如下:
A a = new A();
SoftReference<A> srA = new SoftReference<A>(a);
软引用所指示的对象进行垃圾回收需要满足如下两个条件:
1.当其指示的对象没有任何强引用对象指向它;
2.当虚拟机内存不足时。
因此,SoftReference变相的延长了其指示对象占据堆内存的时间,直到虚拟机内存不足时垃圾回收器才回收此堆内存空间。
3. 弱引用(Weak Reference)
同样的,软引用的一般使用形式如下:
A a = new A();
WeakReference<A> wrA = new WeakReference<A>(a);
WeakReference不改变原有强引用对象的垃圾回收时机,一旦其指示对象没有任何强引用对象时,此对象即进入正常的垃圾回收流程。
4. 虚引用(Phantom Reference)
与SoftReference或WeakReference相比,PhantomReference主要差别体现在如下几点:
1.PhantomReference只有一个构造函数
PhantomReference(T referent, ReferenceQueue<? super T> q)
2.不管有无强引用指向PhantomReference的指示对象,PhantomReference的get()方法返回结果都是null。
因此,PhantomReference使用必须结合ReferenceQueue;
与WeakReference相同,PhantomReference并不会改变其指示对象的垃圾回收时机。
内存泄露原因:
如果持有对象的强引用,垃圾回收器是无法在内存中回收这个对象。
内存泄露的真因是:持有对象的强引用,且没有及时释放,进而造成内存单元一直被占用,浪费空间,甚至可能造成内存溢出!
其实在Android中会造成内存泄露的情景无外乎两种:
- 全局进程(process-global)的static变量。这个无视应用的状态,持有Activity的强引用的怪物。
- 活在Activity生命周期之外的线程。没有清空对Activity的强引用。
---------------------------------------华丽的分割线-----------------------------------
重点来了√√√√√√√√√√√
常见内存泄露解决办法:
1.设置了监听器,退出后没有反注册
很好解决,在activity退出时记得removeListener即可;
2.内部类导致的内存泄漏
内部类经常涉及到需要访问外部类的对象,通过该对象引用外部类的属性或者方法,当该内部类被一个长生命周期的实例引用时,就会导致外部类也无法释放,这是导致内存泄漏的最主要原因。
由于内部类导致的内存泄漏有一套通用的解决方案:对外部类的对象使用弱引用,内部类static化。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
static class A {
WeakReference<MainActivity> reference;
public A(MainActivity ref){
reference = new WeakReference<MainActivity>(ref);
}
public void test(){
MainActivity activity = reference.get();
if(activity == null){
return;
}
}
}
}
3.Dialog依附的Activity销毁了,但是忘记做dialog的dismiss操作。
记得在退出时,把依附于该Activity的Dialog销毁。
4.Webview导致的内存泄漏
在一些机型上会出现Webview相关的泄漏,即使调用webview的destroy也无法销毁,可以将webview相关的业务移至另一个进程,通过aidl完成通讯。其实这种新开进程的方案也适用于那些相对独立,并且需要耗费大内存的业务。
5.Handler导致的泄漏
-1:泄漏的路径为:Thread->Looper->MessageQueue -> Message ->handler->Activity
定义的Runnable对象会间接地引用定义它的Activity对象,而它会被提交到Handler的MessageQueue中,如果它在Activity销毁时还没有被处理,那就会导致内存泄漏了。
解决办法是:退出时remove掉消息队列中的message或者Runnable。
-2:泄漏路径为:Thread->Looper->MessageQueue ->mIdleHandlers ->handler->Activity
解决办法是:UI退出时主动调用 removeIdleHandler。
附:Leaks工具-检查内存泄漏,内存清理、监控
总结
虽然现在手机内存在不停的提升,内存泄露兴许不会像dalvik时代由于虚拟机内存过小造成各种花样oom。但是过量的内存泄露依然会造成内存溢出,影响用户体验,在如今定制系统层出不穷、机型花样越来越多的情况下解决好内存泄露的问题会让适配和稳定性进一步提高!
希望大家看完有所收获,不足的地方指给我,我也会改进!
- Android内存泄露解析和优化
- Android性能优化方法和内存泄露
- android内存泄露案例和解析
- Android内存泄露案例和解析
- android内存泄露优化
- Android内存泄露优化
- Android内存泄露解析
- Android内存优化方案和内存泄露检测分析方法
- android内存泄露优化总结
- android内存泄露优化总结
- Android内存泄露优化总结
- android内存泄露优化总结
- android内存泄露优化总结
- Android内存泄露优化总结
- android内存泄露优化总结
- android内存泄露优化总结
- Android内存泄露优化总结
- android内存泄露优化总结
- JavaScript基础—鼠标移入移出
- npm install 安装过程卡住不动
- 返回数组最左边位置
- Oracle CASE WHEN 用法介绍(转)
- ORACLE 常用函数——日期/时间函数
- Android内存泄露解析和优化
- 新手学web之CSS快速了解及入门
- spring security四种实现方式
- **一个模仿百度的案例**
- ckeditor内容和表单一起提交
- java8 lambda表达式 编写jdbc基类
- 屏幕适配
- redis配置命令
- 公众号开启开发者模式