一、基础知识
1.1、内存泄露、内存溢出:
内存泄露(Memory Leak)指一个无用对象持续占有内存或无用对象的内存得不到及时的释放,从而造成内存空间的浪费
例如,当Activity的onDestroy()方法被调用以后,Activity 本身以及它涉及到的 View、Bitmap等都应该被回收。但是,如果有一个后台线程持有对这个Activity的引用,那么Activity占据的内存就不能被回收,严重时将导致OOM,最终Crash。
内存溢出(Out Of Memory)指一个应用在申请内存时,没有足够的内存空间供其使用
相同点:都会导致应用运行出现问题、性能下降或崩溃。
不同点:
内存泄露是导致内存溢出的原因之一,内存泄露严重时将导致内存溢出
内存泄露是由于软件设计缺陷引起的,可以通过完善代码来避免;内存溢出可以通过调整配置来减少发生频率,但无法彻底避免
1.2、Java 的内存分配:
静态存储区:在程序整个运行期间都存在,编译时就分配好空间,主要用于存放静态数据和常量
栈区:当一个方法被执行时会在栈区内存中创建方法体内部的局部变量,方法结束后自动释放内存
堆区:通常存放 new 出来的对象,由 Java 垃圾回收器回收
1.3、四种引用类型:
强引用(StrongReference):Jvm宁可抛出 OOM (内存溢出)也不会让 GC(垃圾回收) 回收具有强引用的对象
软引用(SoftReference):只有在内存空间不足时才会被回收的对象
弱引用(WeakReference):在 GC 时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
虚引用(PhantomReference):任何时候都可以被GC回收,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否存在该对象的虚引用,来了解这个对象是否将要被回收。可以用来作为GC回收Object的标志。
内存泄漏就是指new出来的Object(强引用)无法被GC回收
1.4、非静态内部类和匿名类:
非静态内部类和匿名类会隐式地持有一个外部类的引用
1.5、静态内部类:
外部类不管有多少个实例,都是共享同一个静态内部类,因此静态内部类不会持有外部类的引用
二、内存泄漏情况分析
2.1、资源未关闭
在使用Cursor,InputStream/OutputStream,File的过程中往往都用到了缓冲,因此在不需要使用的时候就要及时关闭它们,以便及时回收内存。它们的缓冲不仅存在于 java虚拟机内,也存在于java虚拟机外,如果只是把引用设置为null而不关闭它们,往往会造成内存泄漏。
此外,对于需要注册的资源也要记得解除注册,例如:BroadcastReceiver。动画也要在界面不再对用户可见时停止。
2.2、Handler
在如下代码中
public class HandlerActivity extends AppCompatActivity {
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
}
}
在声明Handler对象后,IDE会给开发者一个提示:
This Handler class should be static or leaks might occur.
意思是:Handler需要声明为static类型的,否则可能产生内存泄漏
这里来进行具体原因分析:
应用在第一次启动时, 系统会在主线程创建Looper对象,Looper实现了一个简单的消息队列,用来循环处理Message。所有主要的应用层事件(例如Activity的生命周期方法回调、Button点击事件等)都会包含在Message里,系统会把Message添加到Looper中,然后Looper进行消息循环。主线程的Looper存在于整