对内存溢出、内存泄漏与内存抖动的学习,予以记录!
OOM产生的原因:
1.内存泄漏
2.频繁申请内存得不到及时的回收
减少OOM的概率:
1.尽可能少的发生内存泄漏
2.尽可能不在循环中申请内存
3.尽可能不在调用次数多的函数中申请内存
内存溢出(Out Of Memory) 就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
内存泄漏(memory leak):指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况
原因:一个长生命周期的对象持有一个短生命周期对象的引用
通俗讲就是该回收的对象,因为引用问题没有被回收,最终会产生OOM
原因:由于单例的静态特性导致它的生命周期和整个应用的生命周期一样长,如果有对象已经不再使用了,但又却被单例持有引用,那么就会导致这个对象就没办法被回收,从而导致内存泄漏。
// 使用了单例模式
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() ,因为应用的上下文对象的生命周期和整个
应用一样长。
由于非静态内部类会默认持有外部类的引用,如果我们在外部类中去创建这个内部类对象,当频繁打开关闭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 )。
原因:首先我们知道程序启动时在主线程中会创建一个Looper对象,这个 Looper 里维护着一个 MessageQueue 消息队列,这个消息队列里会按时间顺序存放着Message ,然后上面的 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<Activity> mActivity;
public MyHandler(Activity activity) {
mActivity = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
if (mActivity.get() == null) {
return;
}
//to do something..
}
};
关于WebView的内存泄漏,这是个绝对的大大大大大坑!不同版本都存在着不同版本的问题,这里我只能给出我平时的处理方法,可能不同机型上存在的差异,只能靠积累了。
方法一:
首先不要在 xml 去定义,定义一个 ViewGroup 就行,然后动态在代码中 new WebView(Context
context) (传入的 Context 采取弱引用),再通过 addView 添加到 ViewGroup 中,最后在页面销毁执行 onDestroy()的时候把 WebView 移除。
方法二:
简单粗暴,直接为 WebView 新开辟一个进程,在结束操作的时候直接 System.exit(0) 结束掉进程,这里需要注意进程间的通讯,可以采取Aidl , Messager , Content Provider , Broadcast 等方式。
这部分和 Handler 比较像,其实也是因为内部类持有外部类引用,一样的改成静态内部类,然后在onDestory 方法中取消任务即可。
这块就比较简单了,比如我们经常使用的广播接收者,数据库的游标,多媒体,文档,套接字等。
还有一些需要注意的,比如注册了 EventBus 没注销,添加 Activity 到栈中,销毁的时候没移除等。
内存抖动(Memory Churn):在短时间内反复地发生内存增长和回收
问题:内存抖动可能导致程序卡顿甚至OOM内存溢出。
卡顿原因:当gc回收内存时,会使当前的工作线程暂停,如果多次内存回收行为集中在短时间内爆发,就有可能造成卡顿
gc检测垃圾对象主要有两种算法:引用计数法和可达性分析法
可以通过下面的代码看出每个应用程序最高可用内存是多少:
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024 /1024);
Log.d("TAG", "Max memory is " + maxMemory+"MB");