什么是内存溢出?
OOM(out of memory)即内存溢出.在程序中,对内存使用超过一定的阀值就会导致内存溢出,而new出来的Object对象在使用完后触发GC也无法被回收 叫做内存泄漏
OOM的可能导致的现象?
1.程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC)
2.由于APP运行内存限制,会导致直接崩溃(OutOfMemoryError)
3.触发Low Memory Killer机制,应用莫名被杀
Android内存进程管理,Low Memory Killer机制
怎么知道内存泄漏呢?
内存泄漏发生时的主要表现为内存抖动,可用内存慢慢变少,在Android studio中可以通过Android Profiler工具查看内存抖动情况
内存溢出(泄漏)如何产生的?:
如何避免:
1.资源性对象及时关闭,使用的任何资源都要及时关闭或者异常处理,保证在最恶劣的情况下资源可以得到释放
(如:Cursor、File、Receiver、Sensor)
2.资源的注册和反注册成对出现(广播,观察者)
如事件注册后未注销,会导致观察者列表中持有Context对象的引用
3.页面退出时及时清理一些资源占用(集合对象,WebView)**
容器中的对象在不用的时候及时清理,WebView存在着内存泄漏的问题,在应用中只要使用一次,WebView,内存就不会被释放掉.
4.资源重复利用(使用adapter的时候使用convertView)**
如何避免:
1.Bitmap没有使用的时候及时recycle释放内存
2.对大图进行压缩,使用软引用或弱引用(使用这两种引用代码需要做不为空判断)
3.使用缓存技术(LruCache和DiskLruCache)
如何避免:
1.尽量避免static成员变量引用资源 消耗过多的实例,比如Context;静态变量不要持有大数据对象
2.使用软引用代替强引用**
3.尽量使用ApplicationContext,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题
4.对于内部类尽量使用静态内部类,避免由于内部类导致的内存泄漏(静态内部类可以通过软引用使用外部的Context)如:Handler使用静态内部类
如何避免:
1.将线程的内部类,改为静态内部类,在线程内部采用弱引用保存Context引用
2.线程优化:避免程序中存在大量的Thread.可以使用线程池,并且页面退出时,终止线程.**
例子: 在activity中handler发一个延时任务,activity退出后,延迟任务的message还在主线程,它持有activity的handler引用,所以造成内存泄漏(handler为非静态类,它会持有外部类的引用,也就是activity); 这里可以把handler声明为static的,则handler的存活和activity生命周期无关了,如果handler内部使用外部类的非static对象(如: Context),应该通过弱引用传入,activity销毁时,移除looper线程中的消息.
常见的内存泄漏案例分析
由于单例模式的静态特性,使得它的生命周期和我们的应用一样长,如果让单例无限制的持有Activity的强引用就会导致内存泄漏
内存泄漏代码片段:
public class MyInstance {
private static MyInstance mMyInstance;
private Context mContext;
private MyInstance(Context context) {
this.mContext = context;
}
public static MyInstance getInstance(Context context) {
if (mMyInstance == null) {
synchronized (MyInstance.class) {
if (mMyInstance == null) {
mMyInstance = new MyInstance(context);
}
}
}
return mMyInstance;
}
private View mView = null;
public void setXXView(View xxView) {
mView = xxView;
}
}
解决方案:
public class MyInstance {
private static MyInstance mMyInstance;
private Context mContext;
private MyInstance(Context context) {
this.mContext = context.getApplicationContext();
}
public static MyInstance getInstance(Context context) {
if (mMyInstance == null) {
synchronized (MyInstance.class) {
if (mMyInstance == null) {
mMyInstance = new MyInstance(context);
}
}
}
return mMyInstance;
}
private WeakReference mView = null;
public void setXXView(View xxView) {
mView = new WeakReference(xxView);
}
}
内存泄漏代码片段:
当Activity退出时,延时任务Message还在主线程的MessageQueue中等待,此时的Message持有Handler的强引用,并且Handler是Activity类的非静态内部类,所以也默认持有Activity的强引用.
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 5000);
}
解决方案: 使用静态内部类,通过弱引用传入外部类的Context
private final Handler mHandler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 5000);
}
static class MyHandler extends Handler {
private SoftReference reference;
public MyHandler(Activity activity) {
// 持有 Activity 的软引用
reference = new SoftReference(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = reference.get();
if (activity != null && !activity.isFinishing()) {
switch (msg.what) {
// 处理消息
}
}
}
}
内部类默认持有外部类强引用,容易出现内存泄漏
内存泄漏代码片段:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
run();
}
public void run() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
SystemClock.setCurrentTimeMillis(1000);
}
}
}).start();
}
解决方案: 使用static,变成静态匿名内部类
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
run();
}
public static void run() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
SystemClock.setCurrentTimeMillis(1000);
}
}
}).start();
}
使用ApplicationContext代替Activity Context ,因为ApplicationContext的生命周期也就是该应用生命周期,不依赖于activity的生命周期
1. MAT工具(很全面,但入手较难,MAT为Eclipse自带工具)
2. Android Profiler(图像化工具,AndroidStudio自带工具)
3. LeakCanary工具(简便)
源码:https://github.com/square/leakcanary
支持Eclipse的库:http://download.csdn.net/detail/ytuglt/9533490
Android内存泄露可能会出现的现象:
1.频繁打开应用会出现界面卡顿,动画不流畅;
2.操作中,Logcat频繁输出GC日志;
例如:
dalvikvm GC_FOR_ALLOC freed 9534k, 16% free 72342k/88240k, paused 43ms, totol 29ms
dalvikvm GC_FOR_ALLOC freed 9534k, 16% free 72342k/88240k, paused 43ms, totol 39ms
dalvikvm GC_FOR_ALLOC freed 9534k, 16% free 72342k/88240k, paused 43ms, totol 39ms
MAT工具关键字段:
Dominator Tree: 可以列出所有对象,以及它们占用的内存大小
Shallow Heap:对象本身的内存大小;
Retained Heap:对象本身以及它持有的所有对象的内存总和;
SystemClass:是指系统管理的对象;
Histogram:列出内存中所有的类,以及内存的实例的个数;(object:内存中实例的个数);
原则:如果一个对象直接或者间接的被GC Roots引用,是不会被系统回收掉,因此我们可以查看xxxx(Bitmap)对象到GC Roots的引用路径来分析问题;
1.左下角有个小圆点,代表着这个对象被GC Roots直接引用;
2.引用流程:
GC Roots 持有着我们创建出来的Thread对象,Thread对象引用了activity的实例,activity的实例引用了我们的bitmap对象,因此当activity退出,Thread仍保持着activity的引用,导致它引用的内存不能被回收. 三丶解决方案 1.将thread移到后台服务,使thread与activity接触依赖 2.activity退出时,停止thread,让thread和activity的生命周期保持一致
常见内存泄露代码: new Thread(){}是一个匿名内部类,会隐式的持有一个外部类的对象MainActivity; 如果MainActivity在Thread执行前就销毁了,那么这个activity实例就发生了内存泄露; 根本原因:长生命周期对象(thread)持有短生命周期对象(activity)的引用,导致内存泄露;
内存泄漏检测:
在执行某种操作后进行一次GC,内存没有明显的回落。此时即可以断定代码中可能存在内存泄漏
相关链接直达:
Android APP性能优化之 ---- 布局优化(一)
Android APP性能优化之 ---- 内存优化(二)
Android APP性能优化之 ---- 代码优化(三)
Android APP性能优化之 ---- 优化监测工具(四)
Android APP性能优化之 ---- APK瘦身 App启动优化
Android内存泄露OOM的原因及解决方法