一.概念
1.JAVA是在JVM所虚拟出的内存环境中运行的,内存分为三个区:堆、栈和方法区。
①.栈(stack):是简单的数据结构,程序运行时系统自动分配,使用完毕后自动释放。优点:速度快。
②.堆(heap):用于存放由new创建的对象和数组。在堆中分配的内存,一方面由java虚拟机自动垃圾回收器来管理,另一方面还需要程序员提供修养,防止内存泄露问题。
③.方法区(method):又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量
2.内存溢出(Out of Memory):系统会给每个APP分配内存也就是Heap Size值。当APP占用的内存加上我们申请的内存资源超过了Dalvik虚拟机的最大内存时就会抛出的Out Of Memory异常。
3.内存泄漏(Memory Leak):当一个对象不在使用了,本应该被垃圾回收器(JVM)回收。但是这个对象由于被其他正在使用的对象所持有,造成无法被回收的结果。内存泄漏最终会导致内存溢出。
4.强引用(StrongReference):当内存空间不足的时候,java虚拟机宁愿抛出OOM异常,使程序异常终止,也不会靠随意回收有强引用的 对象来解决内存不足的问题,如果这个对象用完不用的时候可以通过 a = null;的这种方式来淡化引用;作用是帮助 垃圾收集器回收此对象;通过这种显示的方式设置a为null,gc会认为该对象不存在引用,这个时候gc就可以回收这个对象。
强应用就是我们平时new的对象 例如:Object object= new Object();
5.软引用(SoftReference):如果一个对象只有软引用,并且内存足够的时候, 垃圾回收期就不会回收它;如果内存不足了,就会被回收这些对象的内存;主要垃圾回收期没有回收,该对象就可以一直被被程序使用。软引用可以被用来实现内存敏感的高速缓存。
public class SoftReferenceTest {
static class HeadObject{
byte[] bs = new byte[1024 * 1024];
}
public static void main(String[] args) {
SoftReference<HeadObject> softReference = new SoftReference<HeadObject>(new HeadObject());
List<HeadObject> list = new ArrayList<>();
while(true){
if(softReference.get()!= null){
list.add(new HeadObject());
System.out.println("list-add");
}else{
System.out.println("----软引用被回收");
break;
}
System.gc();
}
}
}
这段程序代码最后的结果是n多个list-add,直到系统回收,最后是—软引用被回收。
6.弱引用(WeakReference):弱引用与软引用的区别在于:只具有弱引用的对象的生命周期更短。在垃圾收集器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管内存足够还是不足,都会进行回收。
public static void main(String[] args) throws InterruptedException {
WeakReference<TestObj> weakReference = new WeakReference(new TestObj());
System.out.println(weakReference.get() == null);
System.gc();
Thread.sleep(1000);
System.out.println(weakReference.get() == null);
}
让代码睡眠了1秒,gc之前的结果为true,gc之后的结果为false
7.虚引用:顾名思义,比较虚,就是形同虚设,又称为幻影引用,与其他几种都不同,虚引用不会决定对象的生命周期。如果一个对象仅是虚引用,那么他就和没有任何引用一样,在任何时候都可能被回收。
8.内存泄漏是造成应用程序OOM的主要原因之一。由于Android系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时,就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用Crash
二.如何避免内存泄漏
1.释放强引用,使用软引用和弱引用。
初始化对象以后,如果不在需要使用,及时释放掉 例如 object=null;
2.图片处理。
①.不要在主线程中处理图片,在子线程当中处理图片的下载以及保存。
②.图片使用后及时释放
Bitmap bitmap= BitmapFactory.decodeFile("file");
if (bitmap.isRecycled()){
bitmap.recycle();
}
③.控制图片的大小,压缩大图,高效处理,加载合适属性的图片。
当加载的ImageView大小不是自适应的情况下,使用Picasso加载时,可以使用resize()方法,重新设置图片的大小,尤其用在ListView列表中,可以防止卡顿甚至oom。
在Listview滑动时,可以不加载图片,等到listview滑动结束后再加载图片。
3.非静态内部类和匿名內部类Handler、Thread、Runnable等由于持有外部类Activity的引用,从而关闭activity,线程未完成造成内存泄漏。
①.在activity中开启一个延时线程,当该activity销毁时,由于线程隐士持有外部类的引用,该线程未被销毁,造成内存泄漏。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
leakFun();
((Button) findViewById(R.id.tr)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this,SecondActivity.class));
finish();
}
});
}
private void leakFun() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10 * 1000);
Log.d(TAG, "run: 10秒后运行了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
日志显示跳转页面后,还会打印线程当中的日志“10秒后运行了”,线程未被释放。
4.集合类泄漏
List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object o = new Object();
objectList.add(o);
o = null;
}
虽然在for循环中把o置为null,但是在objectList集合中仍然持有对像的引用,所以该对象还是无法被回收的。需要在objectList使用完后清空
objectList.clear();
objectList=null;
5.单例造成的内存泄漏,上下文范围
由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏
public class SingleInstanceClass {
private static SingleInstanceClass instance;
private Context mContext;
//在构造方法中传入上下文
private SingleInstanceClass(Context context) {
this.mContext = context;
}
public SingleInstanceClass getInstance(Context context) {
if (instance == null) {
instance = new SingleInstanceClass(context);
}
return instance;
}
}
正如前面所说,静态变量的生命周期等同于应用的生命周期,此处传入的Context参数便是祸端。如果传递进去的是Activity或者Fragment,由于单例一直持有它们的引用,即便Activity或者Fragment销毁了,也不会回收其内存。特别是一些庞大的Activity非常容易导致OOM。
正确的写法应该是传递Application的Context,因为Application的生命周期就是整个应用的生命周期,所以没有任何的问题。
private SingleInstanceClass(Context context) {
this.mContext = context.getApplicationContext();// 使用Application 的context
}
6.匿名内部类/非静态内部类/异步线程/Handler
①.我们都知道非静态内部类是默认持有外部类的引用的,如果在内部类中定义单例实例,会导致外部类无法释放。如下面代码
public class TestActivity extends AppCompatActivity {
public static InnerClass innerClass = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (innerClass == null)
innerClass = new InnerClass();
}
private class InnerClass {
//...
}
}
当TestActivity销毁时,因为innerClass生命周期等同于应用生命周期,但是它又持有TestActivity的引用,因此导致内存泄漏。
正确做法应将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请按照上面推荐的使用Application 的 Context。
②.android开发经常会继承实现Activity/Fragment/View,此时如果你使用了匿名类,并被异步线程持有了,那要小心了,如果没有任何措施这样一定会导致泄露。如下代码:
public class TestActivity extends AppCompatActivity {
//内部类的创建....
private Runnable runnable=new Runnable() {
@Override
public void run() {
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//......
}
}
上面的runnable所引用的匿名内部类持有TestActivity的引用,当将其传入异步线程中,线程与Activity生命周期不一致就会导致内存泄漏。
③。Handler造成内存泄漏的根本原因是因为,Handler的生命周期与Activity或者View的生命周期不一致。Handler属于TLS(Thread Local Storage)生命周期同应用周期一样。看下面的代码:
public class TestActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void dispatchMessage(Message msg) {
super.dispatchMessage(msg);
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
//do your things
}
}, 60 * 1000 * 10);
finish();
}
}
在该TestActivity中声明了一个延迟10分钟执行的消息 Message,mHandler将其 push 进了消息队列 MessageQueue 里。当该 Activity 被finish()掉时,延迟执行任务的Message 还会继续存在于主线程中,它持有该 Activity 的Handler引用,所以此时 finish()掉的 Activity 就不会被回收了从而造成内存泄漏(因 Handler 为非静态内部类,它会持有外部类的引用,在这里就是指TestActivity)。
修复方法:采用内部静态类以及弱引用方案。代码如下:
public class TestActivity extends AppCompatActivity {
private MyHandler mHandler;
private static class MyHandler extends Handler {
private final WeakReference<TestActivity> mActivity;
public MyHandler(TestActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void dispatchMessage(Message msg) {
super.dispatchMessage(msg);
TestActivity activity = mActivity.get();
//do your things
}
}
private static final Runnable mRunnable = new Runnable() {
@Override
public void run() {
//do your things
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new MyHandler(this);
mHandler.postAtTime(mRunnable, 1000 * 60 * 10);
finish();
}
}
创建一个静态Handler内部类,然后对 Handler 持有的对象使用弱引用,这样在回收时也可以回收 Handler 持有的对象,但是这样做虽然避免了Activity泄漏,不过Looper 线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。
public final void removeCallbacks(Runnable r);
public final void removeCallbacks(Runnable r, Object token);
public final void removeCallbacksAndMessages(Object token);
public final void removeMessages(int what);
public final void removeMessages(int what, Object object);
7.尽量避免使用 staic 成员变量
如果成员变量被声明为 static,那我们都知道其生命周期将与整个app进程生命周期一样
8.广播未注销引起内存泄漏。应该在activity的OnDestroy()方法中注销广告 unregisterReceiver(receiver);
9.资源未关闭或释放导致内存泄露 。在使用IO、File流或者Sqlite、Cursor等资源时要及时关闭。这些资源在进行读写操作时通常都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。
10.WebView造成内存泄露。
关于WebView的内存泄露,因为WebView在加载网页后会长期占用内存而不能被释放,因此我们在Activity销毁后要调用它的destory()方法来销毁它以释放内存。Android5.1之后,直接调用webView.destroy()方法无法彻底解决,最终解决方案为:在销毁WebView之前需要先将WebView从父容器中移除,然后在销毁WebView
mWebViewContainer.removeView(mWebView);
mWebView.stopLoading();
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.removeAllViews();
mWebView.destroy();