两个概念:
内存泄漏:可以解释为,内存不在GC掌控之内。
OOM: 即out of memory内存溢出(当应用占用的heap资源超过了Dalvik虚拟机分配的内存就会内存溢出),内存泄漏多了就会导致内存溢出。
1.什么是内存泄漏?
当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致对象不能被回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏。
首先了解一下内存分配的几种策略:
1.静态区
静态的存储区:内存在程序编译的时候就已经分配好,这块的内存在程序整个运行期间都一直存在。它主要存放静态数据、全局的static数据和一些常量。
2.栈区
栈区中存储的数据是函数(方法)在执行时,函数一些内部变量的存储放在栈上面创建,函数执行结束的时候这些存储单元就会自动被释放掉。当然提前说一下,在方法内部实例化的对象即:通过new关键字创建的对象是保存在堆中,对象的引用是在栈里面。栈内存包括分配的运算速度很快,因为内置在处理器的里面的。当然容量有限
3.堆区
堆是不连续的内存区域,堆空间比较灵活也特别大。栈是一块连续的内存区域,大小是有操作系统决定的,我们所说的内存泄漏都是发生在堆里,因为栈里的数据在函数执行完成之后已经进行了销毁,所以堆是出现内存泄漏的地方,堆里面存放的数据就是我们new出来的东西了。
堆管理很麻烦,频繁地new/remove会造成大量的内存碎片,这样就会慢慢导致效率低下。对于栈的话,他先进后出,进出完全不会产生碎片,运行效率高且稳定。
public class Main{
inta = 1;
Students = new Student();//堆里面
publicvoid method(){
intb = 1;//栈里面
Students2 = new Student();
}
}
在Java中:
1.成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体)---因为他们属于类,类对象最终还是要被new出来的。
2.局部变量的基本数据类型和引用存储于栈当中,引用的对象实体存储在堆中。-----因为他们属于方法当中的变量,生命周期会随着方法一起结束。
所以,我们一般所讨论的内存泄漏主要讨论堆内存,他存放的就是引用指向的对象实体。
上面提到内存不在GC掌控之内即会出现内存泄漏,那么什么是垃圾回收机制:
GC是垃圾回收机制:在C语言中内存的申请和释放需要程序员手动去执行(malloc free),但是在Java中则是由GC垃圾回收机制来回收,它是一个守护进程。但是GC本身也很耗性能,容易造成内存抖动和动荡,所以在开发中一定要及时释放内存。
总结:当某对象不在由任何的引用的时候GC才会进行回收,否则会导致内存泄漏。
(这里的没有任何引用指的是无法追溯到GC ROOTS的引用,如下图Object G和Object H虽然有引用,但是无法追溯到GC ROOTS,所以也属于GC可以回收的对象)
另外,弱应用、软应用也是可以被GC回收的对象。
那么,问题来了,什么对象可以被作为GC ROOTS 引用点的呢?
2.可以作为GC ROOTS引用点的是:
JavaStack中的引用的对象
方法区中静态引用指向的对象
方法区中常量引用指向的对象
Native方法中JNI引用的对象
Thread---活着的线程
3.怎么判断是垃圾对象?
GC能回收的
GC无能力回收的,认为造成的 --- 也就是泄漏的对象
4.确定是否存在内存泄漏
1)AndroidMonitors的内存分析
最直观的看内存增长情况,知道该动作是否发生内存泄露。
动作发生之前:GC完后内存; 动作发生之后:GC完后内存,对比前后内存的变化
2)使用MAT内存分析工具
MAT分析heap的总内存占用大小来初步判断是否存在泄露
Heap视图中有一个Type叫做data object,即数据对象,也就是我们的程序中大量存在的类类型的对象。
在data object一行中有一列是“TotalSize”,其值就是当前进程中所有Java数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏。
我们反复执行某一个操作并同时执行GC排除可以回收掉的内存,注意观察data object的Total Size值,正常情况下Total Size值都会稳定在一个有限的范围内,也就是说由于程序中的代码良好,没有造成对象不被垃圾回收的情况。反之如果代码中存在没有释放对象引用的情况,随着操作次数的增多Total Size的值会越来越大。
那么这里就已经初步判断这个操作导致了内存泄露的情况。
5.查找内存泄漏方式
总结起来就两个步骤:
1.先找怀疑对象(哪些对象属于泄露的)
MAT对比操作前后的hprof来定位内存泄露是泄露了什么数据对象。(这样做可以排除一些对象,不用后面去查看所有被引用的对象是否是嫌疑)快速定位到操作前后所持有的对象哪些是增加了(GC后还是比之前多出来的对象就可能是泄露对象)
技巧:Histogram中还可以对对象进行Group,比如选择Group ByPackage更方便查看自己Package中的对象信息。
2.MAT分析hprof来定位内存泄露的原因所在。(哪个对象持有了上面怀疑出来的发生泄露的对象)
1)Dump出内存泄露“当时”的内存镜像hprof,分析怀疑泄露的类;
2)把上面1得出的这些可疑点一个一个排查个遍。步骤:
(1)进入Histogram,过滤出某一个嫌疑对象类
(2)然后分析持有此类对象引用的外部对象(在该类上面点击右键List Objects--->with incoming references)
(3)再过滤掉一些弱引用、软引用、虚引用,因为它们迟早可以被GC干掉不属于内存泄露(在类上面点击右键Merge Shortest Paths to GC Roots--->exclude all phantom/weak/softetc.references)
(4)逐个分析每个对象的GC路径是否正常
6.常见的内存泄漏
(1)静态变量引起的内存泄露
当调用getInstance时,如果传入的context是Activity的context。只要这个单例没有被释放,那么这个Activity也不会被释放一直到进程退出才会释放。
代码如下:
public class CommUtil {
private static CommUtil instance;
private Context context;
private CommUtil(Context context){
this.context = context;
}
public static CommUtil getInstance(Context mcontext){
if(instance == null){
instance = new CommUtil(mcontext);
}
return instance;
}
(2)非静态内部类引起内存泄露(包括匿名内部类)
public void loadData(){//隐式持有MainActivity实例。MainActivity.this
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
解决方案:
将非静态内部类修改为静态内部类。(静态内部类不会隐式持有外部类)
(3)不需要用的监听未移除会发生内存泄露
eg1: 对于一些事件的监听要及时移除
tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean b) {
//do something
//处理完后,一定要移除这个监听
tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
}
});
eg2: 对于一些传感器的监听要及时移除
SensorManager sensorManager = getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);
//不需要用的时候记得移除监听
sensorManager.unregisterListener(listener);
(4) 资源未关闭引起的内存泄露情况
比如:BroadCastReceiver、Cursor、Bitmap、IO流、自定义属性attributeattr.recycle()回收。
当不需要使用的时候,要记得及时释放资源。否则就会内存泄露。
(5) 无限循环动画, 如果没有在onDestroy中停止动画,则Activity就会变成泄露对象。
7.如何避免内存溢出
对于6中的集中常见的溢出情况应采取相应方法来避免外,在开发中,为了防止内存溢出,处理一些比较占用内存大并且生命周期长的对象的时候,可以尽量使用软引用和弱引用,或者使用三级缓存(内存缓存 + 外部缓存(SD卡) + 网络存储)。另外在程序中new出来对象,用完后应该主动将对象置为null。
说一下强弱软虚引用的回收时机:
StrongReference强引用:
回收时机:从不回收 使用:对象的一般保存 生命周期:JVM停止的时候才会终止
SoftReference,软引用
回收时机:当内存不足的时候;使用:SoftReference<String>结合ReferenceQueue构造有效期短;生命周期:内存不足时终止
WeakReference,弱引用
回收时机:在垃圾回收的时候;使用:同软引用; 生命周期:GC后终止
PhatomReference 虚引用
回收时机:在垃圾回收的时候;使用:合ReferenceQueue来跟踪对象呗垃圾回收期回收的活动; 生命周期:GC后终止//例如防止activity退出后依然被引用,则可使用WeakReference来避免
public class UIHndler{
WeakReference<MainActivity> softReference;
public UIHndler(MainActivity mainActivity){
softReference=new WeakReference<MainActivity>(mainActivity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity mainActivity = softReference.get();
if(mainActivity!=null){
// 更新ui
mainActivity.handler();
}
}
}