Android内存泄漏分析

摘要:

内存泄漏,即Memory Leak,指程序中不再使用到的对象因某种原因而无法被GC正常回收。它会导致一些不再使用到的对象没有及时释放,这些对象占据着宝贵的内存空间,很容易导致后续分配内存的时候,内存空间不足而出现OOM(内存溢出)。无用对象占据的空间越多,那么可用的空闲空间也就越少,GC就会更容易被触发,GC进行时会停止其他线程的工作,因此有可能造成卡顿等情况。

Java内存分配策略

Java程序运行时的内存分配策略有三种,分别是静态分配、栈式分配和堆分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区。

  • 静态存储区(方法区):主要存放静态数据、全局static数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。
  • 栈区:当方法被执行时,方法体内的局部变量都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。
  • 堆区:又称动态内存分配,通常就是指在程序运行时直接new出来的内存。这部分内存在不使用时将会由Java垃圾回收器来负责回收。
public class Sample {
    
    int s1 = 0;
    Sample mSample1 = new Sample();

    public void method() {
        int s2 = 1;
        Sample mSample2 = new Sample();
    }
}

Sample mSample3 = new Sample();

说明:

  • 局部变量s2和引用变量mSample2都位于栈中,但是mSample2指向的对象是存在于堆上的;
  • mSample3保存于栈中,而其指向的对象实体存放在堆上,包括这个对象的所有成员变量s1和mSample1。

Java是如何管理内存

Java的内存管理就是对象的分配和释放问题。在Java中,通过关键字new为每个对象申请内存空间,所有的对象都在堆(Heap)中分配空间,对象的释放是由GC决定和执行的。

GC(Garbage Collection)
即垃圾回收机制,在Java虚拟机上运行的一个程序,它会监控对象的使用,将不再使用的对象释放,回收内存。

Java判断对象是否可以回收使用的是可达性分析算法。

可达性分析算法:通过一系列被称为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC
Roots没有任何引用链相连时(就是从GC Roots到这个对象是不可达),则证明此对象是不可用的,所以它们会被判断为可回收对象。

在Java语言中,可以作为GC Roots的对象有如下几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈中JNI(Native方法)引用的对象。
    GC

Java中的引用

在Java中,将引用方式分为:强引用、软引用、弱引用、虚引用,这四种引用强度依次逐渐减弱。

强引用:类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

软引用:用来描述一些还有用但并非必须的对象。在系统将要发生内存溢出之前,将会把这些对象列进回收范围之中进行第二次回收。

弱引用:用户描述非必须对象的。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

虚引用:一个对象是否有虚引用存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是能在这个对象被收集器回收时刻得到一个系统通知。

内存泄漏的场景

静态变量内存泄漏

静态变量的生命周期跟整个程序的生命周期一致。只要静态变量没有被销毁也没有置为null,其对象就一直被保持引用,也就不会被垃圾回收,从而出现内存泄漏。

// MainActivity.java
public class MainActivity extends AppCompatActivity {

    private static Test sTest;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sTest = new Test(this);
    }
}

// Test.java
public class Test {
    private Context context;

    public Test(Context context) {
        this.context = context;
    }
}

说明:sTest作为静态变量,并且持有Activity的引用,sTest的生命周期肯定比Activity的生命周期长。因此当Activity退出后,由于Activity仍被sTest引用到,所以Activity就不能被回收,造成了内存泄漏。

Activity这种占用内存非常多的对象,内存泄漏的话影响非常大。

解决方案:

  • 针对静态变量
    在不使用静态变量时置为空,如:
sTest = null;
  • 针对Context
    如果用到Context,尽量去使用Application的Context,避免直接传递Activity,如:
sTest = new Test(getApplicationContext());
  • 针对Activity
    若一定要使用Activity,建议使用弱引用或软引用来代替强引用。如:
// 弱引用
WeakReference<Activity> weakReference = new WeakReference<>(this);
Activity activity = weakReference.get();

// 软引用
SoftReference<Activity> softReference = new SoftReference<>(this);
Activity activity = softReference.get();

单例内存泄漏

单例模式其生命周期跟应用一样,所以使用单例模式时传入的参数需要注意一下,避免传入Activity等对象造成内存泄漏。

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的生命周期的长短至关重要;

  • 如果传入的是Application的Context,因为Application的生命周期就是整个应用的生命周期,所以这将没有任何问题。
  • 如果传入的是Activity的Context,当这个Context所对应的Activity退出时,由于该Context的引用被单例所持有,其生命周期等于整个应用程序的生命周期,所以当前Activity退出时它的内存并不会被回收,这就造成泄漏了。

解决方案

使用和单例生命周期一样的对象。

public class AppManager {
    private static AppManager instance;
    private Context context;

    private AppManager(Context context) {
        this.context = context.getApplicationContext(); // 使用Application的context
    }

    public static AppManager getInstance(Context context) {
        if (instance == null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

非静态内部类(匿名类)内存泄漏

非静态内部类(匿名类)默认就持有外部类的引用,当非静态内部类(匿名类)对象的生命周期比外部类对象的生命周期长时,就会导致内存泄漏

Handler内存泄漏

如果Handler中有延迟任务或者等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。

1、首先,非静态的Handler类会默认持有外部类的引用,如Activity等。

2、然后,还未处理完的消息(Message)中会持有Handler的引用。

3、还未处理完的消息会处于消息队列中,即消息队列MessageQueue会持有Message的引用。

4、消息队列MessageQueue位于Looper中,Looper的生命周期跟应用一致。

引用链:Looper -> MessageQueue -> Message -> Handler -> Activity

解决方法

  • 静态内部类+弱引用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值