Android 内存泄露,怎样查找,怎么产生的内存泄露?

简介: Android 内存泄露,怎样查找,怎么产生的内存泄露?

内存泄露,怎样查找,怎么产生的内存泄露?

    应用开发中经常会碰到一些运行时异常,大多数开发者平时为了满足产品大佬各种各样的idea也是绞尽脑汁,加班加点完成手头中的任务,若开发没有良好的编码习惯或者不太注意一些引用问题,内存回收造成一定的困难就导致内存泄漏,想要让手机不太卡,就需要合理利用应用内存,那下面就简要的说一下内存泄漏问题以及如何排查;欢迎大家留言指正

一,资源对象没关闭造成的内存泄漏

1,描述:资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。因为有些资源性对象,比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。

程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。

二,非静态内部类和匿名內部类Handler、Thread、Runnable等由于持有外部类Activity的引用,从而关闭activity,线程未完成造成内存泄漏

  • 在Activity中创建非静态内部类,非静态内部类会持有Activity的隐式引用,若内部类生命周期长于Activity,会导致Activity实例无法被回收。(屏幕旋转后会重新创建Activity实例,如果内部类持有引用,将会导致旋转前的实例无法被回收)。
  • 如果一定要使用内部类,就改用static内部类,在内部类中通过WeakReference的方式引用外界资源。对Handler、Thread、Runnable等使用弱引用,并且调用removeCallbacksAndMessages等移除。
    举例:在下面这段代码中存在一个非静态的匿名类对象Thread,会隐式持有一个外部类的引用MainActivity 。同理,若是这个Thread作为MainActivity的内部类而不是匿名内部类,他同样会持有外部类的引用。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        leakFun();
    }
    private void testFun() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

在线程休眠的这10s内,会一直隐式持有外部类的引用MainActivity,如果在10s之前,销毁MainActivity,就会报内存泄漏。同理,若是这个Thread作为MainActivity的内部类而不是匿名内部类,也会内存泄漏。
总而言之:如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。
解决办法:在这里只需要将为Thread匿名类定义成静态的内部类即可(静态的内部类不会持有外部类的一个隐式引用)。或保证在Activity在销毁之前,完成任务


三,如果使用Context ,尽可能使用Applicaiton的Context

  • 单例模式造成的内存泄漏,如context的使用,单例中传入的是activity的context,在关闭activity时,activity的内存无法被回收,因为单例持有activity的引用。

  • 在context的使用上,应该传入application的context到单例模式中,这样就保证了单例的生命周期跟application的生命周期一样。

  • 因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。

  • 单例模式应该尽量少持有生命周期不同的外部对象,一旦持有该对象的时候,必须在该对象的生命周期结束前null
public class TestManager {
    private static TestManager instance;
    private Context context;
    private TestManager(Context context) {
        this.context = context;
    }
    public static TestManager getInstance(Context context) {
        if (instance != null) {
            instance = new TestManager(context);
        }
        return instance;
    }

这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
1、传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样长 ;
2、传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存并不会被回收,因为单例对象持有该Activity的引用。
所以正确的单例应该修改为下面这种方式:

public class TestManager {
    private static TestManager instance;
    private Context context;
    private TestManager(Context context) {
//使用全局上下文
      this.context = context.getApplicationContext();
    }
    public static TestManager getInstance(Context context) {
        if (instance != null) {
            instance = new TestManager(context);
        }
        return instance;
    }
}

这样不管传入什么Context最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏。

四,构造Adapter时,没有使用缓存的convertView

以构造ListView的BaseAdapter为例,在BaseAdapter中提供了方法:public View getView(int position, ViewconvertView, ViewGroup parent) 来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的 view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。由此可以看出,如果我们不去使用 convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。ListView回收list item的view对象的过程可以查看: android.widget.AbsListView.java --> voidaddScrapView(View scrap) 方法。示例代码:

public View getView(int position, ViewconvertView, ViewGroup parent) {
        View view = new Xxx(...);
... ...
        return view;
    }
    修正示例代码:
    public View getView(int position, ViewconvertView, ViewGroup parent) {
        View view = null;
        if (convertView != null) {
            view = convertView;
            populate(view, getItem(position));
...
        } else {
            view = new Xxx(...);
...
        }
        return view;
    }


五,集合中对象没清理造成的内存泄漏

通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了


以上只是简单列举一部分常见造成内存泄漏的情况,在开发过程可能不止这些,写代码是还是要有一些Java内存管理的机制,能够深入的了解Android系统内存的分配支出,让自己的应用更加流畅起来,接下来我们说说如何发现代码中更多的内存泄漏问题,使用工具将更快知道哪里有内存泄漏

工具:使用Android Studio 自带的AndroidProfiler工具或MAT,也可以使用Square产品的LeakCanary


1,使用AndroidProfiler的MEMORY工具:

运行程序,对每一个页面进行内存分析检查。首先,反复打开关闭页面5次,然后收到GC(点击Profile MEMORY左上角的垃圾桶图标),如果此时total内存还没有恢复到之前的数值,则可能发生了内存泄露。此时,再点击Profile MEMORY左上角的垃圾桶图标旁的heap dump按钮查看当前的内存堆栈情况,选择按包名查找,找到当前测试的Activity,如果引用了多个实例,则表明发生了内存泄露

2、使用MAT:

1、运行程序,所有功能跑一遍,确保没有改出问题,完全退出程序,手动触发GC,然后使用adb shell dumpsys meminfo packagename -d命令查看退出界面后Objects下的Views和Activities数目是否为0,如果不是则通过Leakcanary检查可能存在内存泄露的地方,最后通过MAT分析,如此反复,改善满意为止。

1)、在使用MAT之前,先使用as的Profile中的Memory去获取要分析的堆内存快照文件.hprof,如果要测试某个页面是否产生内存泄漏,可以先dump出没进入该页面的内存快照文件.hprof,然后,通常执行5次进入/退出该页面,然后再dump出此刻的内存快照文件.hprof,最后,将两者比较,如果内存相除明显,则可能发生内存泄露。(注意:MAT需要标准的.hprof文件,因此在as的Profiler中GC后dump出的内存快照文件.hprof必须手动使用android sdk platform-tools下的hprof-conv程序进行转换才能被MAT打开)

2)、然后,使用MAT打开前面保存的2份.hprof文件,打开Overview界面,在Overview界面下面有4中action,其中最常用的就是Histogram和Dominator Tree。

Dominator Tree:支配树,按对象大小降序列出对象和其所引用的对象,注重引用关系分析。选择Group by package,找到当前要检测的类(或者使用顶部的Regex直接搜索),查看它的Object数目是否正确,如果多了,则判断发生了内存泄露。然后,右击该类,选择Merge Shortest Paths to GC Root中的exclude all phantom/weak/soft etc.references选项来查看该类的GC强引用链。最后,通过引用链即可看到最终强引用该类的对象。

Histogram:直方图注重量的分析。使用方式与Dominator Tree类似。

3)、对比hprof文件,检测出复杂情况下的内存泄露:

通用对比方式:在Navigation History下面选择想要对比的dominator_tree/histogram,右击选择Add to Compare Basket,然后在Compare Basket一栏中点击红色感叹号(Compare the results)生成对比表格(Compared Tables),在顶部Regex输入要检测的类,查看引用关系或对象数量去进行分析即可。

针对于Historam的快速对比方式:直接选择Histogram上方的Compare to another Heap Dump选择要比较的hprof文件的Historam即可


3,LeakCanary  

LeakCanary是一款开源的内存泄漏检查工具,在项目中,可以使用它来检测Activity是否能够被GC及时回收。github的地址为https://github.com/square/leakcanary

相关文章
|
1月前
|
编解码 算法 Java
构建高效的Android应用:内存优化策略详解
随着智能手机在日常生活和工作中的普及,用户对移动应用的性能要求越来越高。特别是对于Android开发者来说,理解并实践内存优化是提升应用程序性能的关键步骤。本文将深入探讨针对Android平台的内存管理机制,并提供一系列实用的内存优化技巧,以帮助开发者减少内存消耗,避免常见的内存泄漏问题,并确保应用的流畅运行。
|
10天前
|
移动开发 Android开发 开发者
构建高效Android应用:采用Kotlin进行内存优化的策略
【4月更文挑战第18天】 在移动开发领域,性能优化一直是开发者关注的焦点。特别是对于Android应用而言,由于设备和版本的多样性,确保应用流畅运行且占用资源少是一大挑战。本文将探讨使用Kotlin语言开发Android应用时,如何通过内存优化来提升应用性能。我们将从减少不必要的对象创建、合理使用数据结构、避免内存泄漏等方面入手,提供实用的代码示例和最佳实践,帮助开发者构建更加高效的Android应用。
|
11天前
|
缓存 移动开发 Java
构建高效的Android应用:内存优化策略
【4月更文挑战第16天】 在移动开发领域,尤其是针对资源有限的Android设备,内存优化是提升应用性能和用户体验的关键因素。本文将深入探讨Android应用的内存管理机制,分析常见的内存泄漏问题,并提出一系列实用的内存优化技巧。通过这些策略的实施,开发者可以显著减少应用的内存占用,避免不必要的后台服务,以及提高垃圾回收效率,从而延长设备的电池寿命并确保应用的流畅运行。
|
1月前
|
缓存 移动开发 Java
构建高效Android应用:内存优化实战指南
在移动开发领域,性能优化是提升用户体验的关键因素之一。特别是对于Android应用而言,由于设备和版本的多样性,内存管理成为开发者面临的一大挑战。本文将深入探讨Android内存优化的策略和技术,包括内存泄漏的诊断与解决、合理的数据结构选择、以及有效的资源释放机制。通过实际案例分析,我们旨在为开发者提供一套实用的内存优化工具和方法,以构建更加流畅和高效的Android应用。
|
1月前
|
监控 Java Android开发
构建高效Android应用:从内存管理到性能优化
【2月更文挑战第30天】 在移动开发领域,打造一个流畅且响应迅速的Android应用是每个开发者追求的目标。本文将深入探讨如何通过有效的内存管理和细致的性能调优来提升应用效率。我们将从分析内存泄露的根本原因出发,讨论垃圾回收机制,并探索多种内存优化策略。接着,文中将介绍多线程编程的最佳实践和UI渲染的关键技巧。最后,我们将通过一系列实用的性能测试工具和方法,帮助开发者监控、定位并解决性能瓶颈。这些技术的综合运用,将指导读者构建出更快速、更稳定、用户体验更佳的Android应用。
|
1月前
|
缓存 监控 API
构建高效的Android应用:从内存优化到电池寿命
【2月更文挑战第27天】 在移动开发领域,构建一个既高效又省电的Android应用是每个开发者的梦想。本文深入探讨了Android应用性能优化的关键策略,包括内存管理和电池使用效率。我们将分析常见的内存泄漏问题,并提供解决方案,同时介绍最新的Android电池优化技术。通过实例和最佳实践,读者将学会如何打造一个更加流畅、响应迅速且电池友好的Android应用。
|
1月前
|
传感器 缓存 Android开发
构建高效的Android应用:从内存优化到电池寿命
【2月更文挑战第23天】在移动开发领域,性能优化是一个持续的挑战。特别是对于Android应用来说,由于设备多样性和碎片化问题,开发者需要采取一系列策略来保证应用的流畅运行。本文深入探讨了Android应用的性能优化,包括内存管理、电池使用效率和UI渲染。我们将提供实用的技巧和最佳实践,帮助开发者构建更加高效、响应迅速的应用,从而改善用户体验并延长设备电池寿命。
14 1
|
1月前
|
监控 Java Android开发
构建高效Android应用:从内存优化到电池寿命
【2月更文挑战第18天】在移动设备的生态系统中,资源管理是确保用户满意度的关键。特别是对于Android开发者来说,优化应用的内存使用和延长电池寿命是提升用户体验的重要方面。本文将深入探讨Android平台上的内存管理机制,提供实用的代码级优化技巧,并讨论如何通过调整应用行为来减少能量消耗,最终目标是为开发者提供一套全面的技术策略,以打造更加高效、响应迅速且电池友好的Android应用。
33 5
|
7月前
|
算法 Java Android开发
Android rxjava和LiveData中的内存泄漏
Android rxjava和LiveData中的内存泄漏
118 0
|
3月前
|
Java 数据库连接 程序员
Android 性能优化: 什么是内存泄漏?如何在Android中避免内存泄漏?
Android 性能优化: 什么是内存泄漏?如何在Android中避免内存泄漏?
70 2