您的位置:首页 > 移动开发 > Android开发

Android 项目中内存泄漏和内存溢出(oom)以及常见内存问题相关Demo详解

2018-03-16 16:24 656 查看

1.什么是内存泄漏?

    内存泄漏指程序向系统申请分配内存空间后,当程序不再使用,内存该释放的不释放,一直会被某个或某些实例所持有,GC垃圾回收机制不再回收所导致的内存泄漏。

2.什么是内存溢出?

    内存溢出是指程序向系统申请分配空间后,当程序请求分配的空间超出系统原有的空间,就会造成内存溢出。(例如一个大杯子的水倒进一个比它小的杯子里,那个小的杯子就会溢出水来,就会造成了内存溢出)
    大量的内存泄漏会导致内存溢出(oom)

内存

想要了解内存泄露,对内存的了解必不可少。
JAVA是在JVM所虚拟出的内存环境中运行的,JVM的内存可分为三个区:堆(heap)、栈(stack)和方法区(method)。栈(stack):是简单的数据结构,但在计算机中使用广泛。栈最显著的特征是:LIFO(Last In, First Out, 后进先出)。比如我们往箱子里面放衣服,先放入的在最下方,只有拿出后来放入的才能拿到下方的衣服。栈中只存放基本类型和对象的引用(不是对象)。堆(heap):堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身。
方法区(method):又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。

引用类型

在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(reachable)状态,程序才能使用它。从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
Java/Android引用类型及其使用分析1. 强引用(Strong reference)
实际编码中最常见的一种引用类型。常见形式如:A a = new A();等。强引用本身存储在栈内存中,其存储指向对内存中对象的地址。一般情况下,当对内存中的对象不再有任何强引用指向它时,垃圾回收机器开始考虑可能要对此内存进行的垃圾回收。如当进行编码:a = null,此时,刚刚在堆中分配地址并新建的a对象没有其他的任何引用,当系统进行垃圾回收时,堆内存将被垃圾回收。
2. 软引用(Soft Reference)
软引用的一般使用形式如下:
A a = new A();
SoftReference<A> srA = new SoftReference<A>(a);
软引用所指示的对象进行垃圾回收需要满足如下两个条件:
1.当其指示的对象没有任何强引用对象指向它;
2.当虚拟机内存不足时。
因此,SoftReference变相的延长了其指示对象占据堆内存的时间,直到虚拟机内存不足时垃圾回收器才回收此堆内存空间。3. 弱引用(Weak Reference)
同样的,软引用的一般使用形式如下:
A a = new A();
WeakReference<A> wrA = new WeakReference<A>(a);
WeakReference不改变原有强引用对象的垃圾回收时机,一旦其指示对象没有任何强引用对象时,此对象即进入正常的垃圾回收流程。4. 虚引用(Phantom Reference)
与SoftReference或WeakReference相比,PhantomReference主要差别体现在如下几点:
1.PhantomReference只有一个构造函数
PhantomReference(T referent, ReferenceQueue<? super T> q)
2.不管有无强引用指向PhantomReference的指示对象,PhantomReference的get()方法返回结果都是null。因此,PhantomReference使用必须结合ReferenceQueue;
与WeakReference相同,PhantomReference并不会改变其指示对象的垃圾回收时机。

内存泄露原因

如果持有对象的强引用,垃圾回收器是无法在内存中回收这个对象。
内存泄露的真因是:持有对象的强引用,且没有及时释放,进而造成内存单元一直被占用,浪费空间,甚至可能造成内存溢出!其实在Android中会造成内存泄露的情景无外乎两种:全局进程(process-global)的static变量。这个无视应用的状态,持有Activity的强引用的怪物。
活在Activity生命周期之外的线程。没有清空对Activity的强引用。
检查一下你的项目中是否有以下几种情况:Static Activities
Static Views
Inner Classes
Anonymous Classes
Handler
Threads
TimerTask
Sensor Manager
最后推荐一个可检测app内存泄露的项目:LeakCanary(可以检测app的内存泄露)

总结

虽然现在手机内存在不停的提升,内存泄露兴许不会像dalvik时代由于虚拟机内存过小造成各种花样oom。但是过量的内存泄露依然会造成内存溢出,影响用户体验,在如今定制系统层出不穷、机型花样越来越多的情况下解决好内存泄露的问题会让适配和稳定性进一步提高!下面就介绍一下最常见、容易犯错的内存泄漏例子:内存泄漏
一、单例造成的内存泄漏Android的单例模式非常受开发者的喜爱,不过使用的不恰当的话也会造成内存泄漏。因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。如下这个典例: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,所以这个Context的生命周期的长短至关重要:1、传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样长 ;2、传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存并不会被回收,因为单例对象持有该Activity的引用。所以正确的单例应该修改为下面这种方式:
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
这样不管传入什么Context最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏。二、关闭Activity之后,线程未关闭造成的内存泄漏import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
Button mBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtn= (Button) findViewById(R.id.mBtn);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}

}点击这个Button按钮之后,等会就会造成内存泄漏。正确做法:import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
Button mBtn;
boolean isFlag=true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtn= (Button) findViewById(R.id.mBtn);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
new Thread(new Runnable() {
@Override
public void run() {
while(isFlag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}

@Override
protected void onDestroy() {
super.onDestroy();
isFlag=false;
}
}
感觉写一个布尔值是最好的做法,当Activity销毁的时候让子线程不再发送消息。据大多帖子说,把线程直接销毁可以解决线程问题,而我感觉写一个布尔值才是最好的做法。(如果说的不对,请大神们多多指点)三、资源未关闭造成的内存泄漏对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。内存溢出:自从Glide出来以后,Glide内存都有自带的压缩功能,由于图片存储过多引起的内存溢出已经解决,还有一些其他的溢出就不一一介绍了,上面知识点总结的有,特别是堆内存,要好好的了解了解。(本人新博客)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android