Android高级工程师之性能篇(1)--内存泄露

案例:
曾经在公司中开发一款师生同屏软件时候,需要教师端实时同步屏幕编码为H264后广播到所有的学生Android平板。在测试过程中,发现反复开启和关闭共享屏幕功能,在30次左右的时候会出现崩溃,调试后发现是由于每一次关闭的时候没有释放线程内存,导致重复创建多个线程实例,出现内存泄露甚至崩溃。

**本篇核心:Java的GC(垃圾回收机制)会自动回收内存。但是当一个对象已经不需要再使用了,本该被自动回收时,而有另外一个正在使用的对象持有它的引用,从而就导致
对象不能被回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏**


java.lang.OutOfMemoryError

内存泄露

在Android开发中我们经常会遇到OutOfMemoryError,俗称OOM或内存溢出。而导致OOM的一个主要原因就是内存泄露(memoryLeak),简单说就是程序申请了内存,却由于某些原因没有在用完时释放此内存空间,堆积后导致OOM。在介绍内存泄露之前,我们需要了解一些背景知识。

1.默认分配内存大小

Android系统为单个APP设置了默认内存大小,一般在16M或24M。但是现在的定制系统如小米魅族等都为单个APP设置了更高的内存,通过定制系统Linux初始化代码里面的Init.c可以查到默认的内存大小。内存泄露的原因便是APP使用的内存超过了这个阈值。
在应用层我们可以在AndroidManifest.xml中android:largeheap = “true”以提高此APP的可使用内存,但是这并不是一个友好的办法,也不能从根本解决内存泄露或者OOM。

".TestApplication"
        android:allowBackup="true"
        android:icon="@drawable/icon"
        android:label="@string/app_name"
        android:largeHeap="true"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
2.内存空间分配–堆栈静态区

通过计算机基础我们知道,通常内存分配有如下3种策略

  • 静态区–核心:static

静态的存储区:内存在程序编译的时候就已经分配好,这块的内存在程序整个运行期间都一直存在。
它主要存放静态数据、全局的static数据和一些常量。

  • 栈—核心:函数中的局部变量、基本类型变量 由编译器自动释放

在执行函数(方法)时,函数一些内部变量的存储都可以放在栈上面创建,函数执行结束的时候这些存储单元就会自动被释放掉。
栈内存包括分配的运算速度很快,因为内置在处理器的里面的,当然容量有限。

  • 堆—核心:成员变量new出来的对象数组等 需要手动释放

也叫做动态内存分配。有时候可以用malloc或者new来申请分配一个内存。在C/C++可能需要自己负责释放(java里面直接依赖GC机制)。我们讨论的内存泄露主要是这个。

Android高级工程师之性能篇(1)--内存泄露_第1张图片

3.四种对象的引用方式

StrongReference,强引用:从不回收 使用:对象的一般保存 生命周期:JVM停止的时候才会终止

SoftReference,软引用:当内存不足的时候;使用:SoftReference结合ReferenceQueue构造有效期短;生命周期:内存不足时终止

WeakReference,弱引用:在垃圾回收的时候;使用:同软引用; 生命周期:GC后终止

PhatomReference 虚引用:在垃圾回收的时候;使用:合ReferenceQueue来跟踪对象呗垃圾回收期回收的活动; 生命周期:GC后终止

4.容易出现内存泄露的地方

1)资源没有关闭(Bitmap等)导致的内存泄漏

bitmap在使用完成之后没有回收,类似的还有BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等。

解决方案:

  • 有BraodcastReceiver在销毁时调用unregister

  • Cursor,Stream、File在不使用时调用close

  • Bitmap对象不在使用时调用recycle

2.单例模式导致内存泄露

在单例模式中如果传入Activity的Context会导致Activity销毁时无法被回收(单例还保持着引用)。

// 使用了单例模式
public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context;
    }
    // 应该传入Application的Context
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

解决方案:单例中传入Application的Context。
注意:旋转手机的时候会不断生成MainActivity实例,在GC的时候会最多保留两个实例,而不是越来越多。

3.Handler/AsyncTask/Runnable导致内存泄露

Handler/AsyncTask/Runnable都使用匿名内部类,持有Activity的引用,在Activity销毁的时候如果任务没完成将导致无法回收Activity。

public class MainActivity extends AppCompatActivity {

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

        new Thread(new TestRunnable()).start();
        new TestAsyncTask(this).execute();
    }

    class TestAsyncTask extends AsyncTask<Void, Void, Void> {

    }

    class TestRunnable implements Runnable {
        @Override
        public void run() {
            // ...
        }
    }
}。

解决方案:使用独立类而不是匿名内部类

4.ListView/GridView等导致的内存泄露

构造Adapter的时候没有复用convertView

内存泄露检测工具

在大多数的运行场景是属于被动型的内存管理,也就是说我们并不知道内存在那里存在泄露,那么,我们需要借助一些工具来进行内存泄露的判断。

1.内存优化前提
  • 某个操作后卡住
  • 重复操作后卡住甚至崩溃
2.内存优化步骤
  • 打开AndroidStudio内存管理Monitor直接分析

这里写图片描述
- 生成hprof文件进行分析

Android高级工程师之性能篇(1)--内存泄露_第2张图片
- 将hprof文件生成标准文件

Android高级工程师之性能篇(1)--内存泄露_第3张图片
- 使用MAT工具
MAT使用教程
- 查找引用了改对象的外部对象有哪些,研究其生命周期是否一致。


这一节到此结束,下一节我们将介绍Android的渲染优化。

==本节相关面试笔试题目:==

  • 请简述堆和栈的区别
  • 内存泄露和内存溢出的区别,根本原因是什么
  • 请描述你在开发工程中遇到的内存泄露问题,你是怎样解决的呢?
  • ListView中渲染多张图片导致内存泄露如何处理。
  • 如何检测内存泄露?

你可能感兴趣的:(内存泄露,android,java,性能,那些扯淡的功能些)