深入Android内存泄露

简介: 深入内存泄露android应用层的内存泄露,其实就是java虚拟机的内存泄漏.(这里,暂不讨论C/C++本地内存的堆泄漏)1.知识储备1.Java内存模型相关内存对象模型,参照博客精讲Java内存模型寄存器(register)。

深入内存泄露

android应用层的内存泄露,其实就是java虚拟机的内存泄漏.
(这里,暂不讨论C/C++本地内存的堆泄漏)


1.知识储备

1.Java内存模型

img_40affdec48d1b678d62634c2bfa1d76f.jpe

相关内存对象模型,参照博客 精讲Java内存模型

  1. 寄存器(register)。这是最快的保存区域,这是主要由于它位于处理器内部。然而,寄存器的数量十分有限,所以寄存器是需要由编译器分配的。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。

(2) 堆栈(stack)在执行函数(方法)时,函数一些内部变量的存储都可以放在栈上面创建,函数执行结束的时候这些存储单元就会自动被释放掉。位于通用RAM(随机访问存储器)中。可通过它的“堆栈指针” 获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。

(3) 堆(heap)。一种通用性的内存池(也在RAM区域),堆是不连续的内存区域,堆空间比较灵活也特别大。其中保存了Java对象(<font color=#FF4500>对象里面的成员变量也在其中</font>)。在堆里分配存储空间时会花掉更长的时间!也叫做动态内存分配。

(4) 静态存储(static storage)。这儿的“静态”(Static)是指“位于固定位置”(尽管也在RAM 里)。程序运行期间,静态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。但Java 对象本身永远都不会置入静态存储空间,随着JVM的生命周期结束而结束,即当app完全退出,他才会释放

(5) 常数存储(constant storage)。常数值通常直接置于程序代码内部。这样做是安全的,因为它们永远都不会改变。

(6) 非RAM 存储(non-storage-RAM)。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“ 流式对象”和“固定对象” 。对于流式对象,对象会变成字节流,通常会发给另一台机器。而对于固定对象,对象保存在磁盘中。

2.GC回收机制

引用自http://blog.csdn.net/jiafu1115/article/details/7024323

首先JVM是对堆进行回收操作.

1.JVM堆中分类

(1) 新域young generation:存储所有新成生的对象

(2) 旧域old generation:新域中的对象,经过了一定次数的GC循环后,被移入旧域

(3) 永久域PermanentGeneration:存储类和方法对象,从配置的角度看,这个域是独立的,不包括在JVM堆内。默认为4M。

2.Gc回收流程

1.当eden满了,触发young GC;

2.young GC做2件事:一,去掉一部分没用的object;二,把老的还被引用的object发到survior里面,等下几次GC以后,survivor再放到old里面。

3.当old满了,触发full GC。full GC很消耗内存,把old,young里面大部分垃圾回收掉。这个时候用户线程都会被block。

3.Gc回收总结

1.JVM堆的大小决定了GC的运行时间。如果JVM堆的大小超过一定的限度,那么GC的运行时间会很长。

2.对象生存的时间越长,GC需要的回收时间也越长,影响了回收速度。

3.大多数对象都是短命的,所以,如果能让这些对象的生存期在GC的一次运行周期内,wonderful!

4.应用程序中,建立与释放对象的速度决定了垃圾收集的频率。

5.如果GC一次运行周期超过3-5秒,这会很影响应用程序的运行,如果可以,应该减少JVM堆的大小了。

6.前辈经验之谈:通常情况下,JVM堆的大小应为物理内存的80%。

3.内存抖动

内存抖动这个术语可用于描述在极短时间内分配给对象的过程.

例如,当你在循环语句中配置一系列临时对象,或者在绘图功能中配置大量对象时,这相当于内循环,当屏幕需要重新绘制或出现动画时,你需要一帧帧使用这些功能,不过它会迅速增加你的堆的压力。

Memory Monitor 内存抖动图例:

img_979496aea72db9cec57167a38a2e73b0.png

2.内存泄漏对程序造成的影响

1.直接:消耗内存,造成系应用OutOfMemory.

一个android应用程序,其实就是一个jvm虚拟机实例,而一个jvm的实例,在初始的时候,大小不等 16M,32M,64M(根据手机厂商和版本不同而不同),当然大小也可以修改,参考修改博客

2.间接:gc回收频繁 造成应用卡顿ANR.

img_103f175dabf3b75ab346afe32793effe.png
GC回收时间过长导致卡顿

首先,当内存不足的时候,gc会主动回收没用的内存.但是,内存回收也是需要时间的.

上图中,android在画图(播放视频等)的时候,draw到界面的对象,和gc回收垃圾资源之间高频率交替的执行.就会产生内存抖动.

很多数据就会污染内存堆,马上就会有许多GCs启动,由于这一额外的内存压力,也会产生突然增加的运算造成卡顿现象

任何线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行,所以垃圾回收运行的次数越少,对性能的影响就越少


3.内存泄露的原因

内存泄漏的本质:不再用到的对象,被错误引用,而无法被回收

未引用对象可以被垃圾回收机制回收,而被引用对象不能被垃圾回收机制回收。
当内存不足,gc会回收垃圾内存
垃圾内存是 没有别人使用的内存,好的内存

内存泄漏 是 正在被别人使用的的内存,不属于垃圾内存

堆引用内存泄漏(Heap leak)

 1.静态变量持有 已经没有用的对象,导致对象无法被回收.例如静态集合类引起内存泄露

 2.单例中持有的引用,当activity重新构建后,单例持有的是上一个activity实例.导致上一个无法被回收.

 3.留意事件监听器和回调.如果一个类注册了监听器,但当该类不再被使用后没有注销监听器,可能会发生内存泄漏。

 4.静态内部类,持有 对象.

 5.Handler 内存泄漏

系统资源泄露(Resource Leak)

主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。在try代码块里创建连接,在finally里释放连接,就能够避免此类内存泄漏。

   1.bitmap资源未释放

   2.IO流未关闭

   3.Cursor使用完后未释放

   4.各种连接(网络,数据库,socket等) 

4.内存泄露的分析工具

在android studio 中有以下几种工具,来进行内存泄漏优化分析(eclipse也有类似工具).

1.Memory Monitor 内存监视器.

img_5cc5d5183f7049c50bfec297b6ae2a5a.png
img_d4f57d3c2c0acfe5fac405a739309712.png

2.Dump java heap

img_4f909785ad6f26eae0be36c944450e47.png

Android Device Monitor(eclipse系列工具类)

img_edc93229050b525a027862cd59d1b9d2.png

第三方库LeakCanary

leakcanary的github地址

screenshot.png

5.内存泄露的实例解决方案

<font color="FF4500">与其说解决内存泄漏,更应该说是 避免内存泄露 .因为内存泄漏一旦产生,即使需要重启JVM,也就是重启应用,内存重新开始计算.即使这样,也没法解决</font>

1.单例造成的内存泄露


/**
 * Created by ccj on 2016/11/3.
 */

public class SingleExample {

    private static SingleExample mExample;
    private Context context;

    private SingleExample(Context context) {
        this.context = context;
    }

    /**
     * 当MainActivity销毁再重建后,此时的context,不会走 if (mExample == null) ,而是直接返回.
     * 此时的Context 还是上一个activity实例的Context,所以,上一个activity实例并未被释放,造成内存泄漏
     * 
     * 此时,只需要将application的上下文,作为context即可解决问题
     * @param context
     * @return
     */
    public static SingleExample getExampleInstance(Context context) {
        if (mExample == null) {
            mExample = new SingleExample(context);
        }
        return mExample;

    }

}

2.匿名内部类 造成的内存泄漏

//android开发经常会继承实现Activity/Fragment/View,此时如果你使用了匿名类,并被异步线程
//持有了,那要小心了,如果没有任何措施这样一定会导致泄露 
    public class MainActivity extends Activity {
        …
        Runnable ref1 = new MyRunable();
        Runnable ref2 = new Runnable() {
            @Override
            public void run() {
            }
        };
        …
    }

3.Handler 造成的内存泄漏

Java对引用的分类有 Strong reference, SoftReference, WeakReference, PhatomReference 四种。

在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。

软/弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。

/*:在 Activity 中避免使用非静态内部类,比如上面我们将 Handler 声明为静态的,
    则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity,
    避免直接将 Activity 作为 context 传进去,

    推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空

*/
public class SampleActivity extends Activity {
    private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);
    // Go back to the previous Activity.
    finish();
    }
}
//改进机制

/*当然在Activity销毁时候也应该取消相应的任务AsyncTask.cancel(),避免任务在后台执行浪费资源*/。
    public class MainActivity extends AppCompatActivity {
        private MyHandler mHandler = new MyHandler(this);
        private TextView mTextView ;
        private static class MyHandler extends Handler {
            private WeakReference<Context> reference;
            public MyHandler(Context context) {
                reference = new WeakReference<>(context);
            }
            @Override
            public void handleMessage(Message msg) {
                MainActivity activity = (MainActivity) reference.get();
                if(activity != null){
                    activity.mTextView.setText("");
                }
            }
        }

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mTextView = (TextView)findViewById(R.id.textview);
            loadData();
        }

        private void loadData() {
//...request
            Message message = Message.obtain();
            mHandler.sendMessage(message);
        }
        //注意释放
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mHandler.removeCallbacksAndMessages(null);
        }
    }

4.非静态内部类创建静态实例造成的内存泄漏

/*这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:
    将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext*/

    public class MainActivity extends AppCompatActivity {
        private static TestResource mResource = null;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            if(mManager == null){
                mManager = new TestResource();
            }
//...
        }
        class TestResource {
//...
        }
    }

5.资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。


6.内存泄漏总结

1、对于生命周期比Activity长的对象如果需要应该使用ApplicationContext

2、在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:
这里写图片描述

img_511433e388f4d35c15daab2ba5e231d2.png

其中:NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建

3、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏

4、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:

将内部类改为静态内部类
静态内部类中使用弱引用来引用外部类的成员变量

5、对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null

6、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期


About Me

github地址
个人技术成长博客

目录
相关文章
|
1月前
|
编解码 算法 Java
构建高效的Android应用:内存优化策略详解
随着智能手机在日常生活和工作中的普及,用户对移动应用的性能要求越来越高。特别是对于Android开发者来说,理解并实践内存优化是提升应用程序性能的关键步骤。本文将深入探讨针对Android平台的内存管理机制,并提供一系列实用的内存优化技巧,以帮助开发者减少内存消耗,避免常见的内存泄漏问题,并确保应用的流畅运行。
|
7月前
|
缓存 Java Shell
Android 内存泄露,怎样查找,怎么产生的内存泄露?
Android 内存泄露,怎样查找,怎么产生的内存泄露?
52 0
|
7天前
|
移动开发 Android开发 开发者
构建高效Android应用:采用Kotlin进行内存优化的策略
【4月更文挑战第18天】 在移动开发领域,性能优化一直是开发者关注的焦点。特别是对于Android应用而言,由于设备和版本的多样性,确保应用流畅运行且占用资源少是一大挑战。本文将探讨使用Kotlin语言开发Android应用时,如何通过内存优化来提升应用性能。我们将从减少不必要的对象创建、合理使用数据结构、避免内存泄漏等方面入手,提供实用的代码示例和最佳实践,帮助开发者构建更加高效的Android应用。
11 0
|
9天前
|
缓存 移动开发 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应用。
31 5
|
7月前
|
算法 Java Android开发
Android rxjava和LiveData中的内存泄漏
Android rxjava和LiveData中的内存泄漏
118 0