内存泄露_内存泄露分析 - CSDN
精华内容
参与话题
  • 什么是内存泄露?怎么检测

    千次阅读 2019-03-20 17:32:03
    什么是内存泄露? 简单地说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它...

    来源:https://blog.csdn.net/u014796694/article/details/80795372

    什么是内存泄露?

    简单地说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。

    2、如何检测内存泄露

    第一:良好的编码习惯,尽量在涉及内存的程序段,检测出内存泄露。当程式稳定之后,在来检测内存泄露时,无疑增加了排除的困难和复杂度。使用了内存分配的函数,一旦使用完毕,要记得要使用其相应的函数释放掉。

    第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链表。

    第三:Boost 中的smart pointer。

    第四:一些常见的工具插件,如ccmalloc、Dmalloc、Leaky等等。

    检测方法:

    无论是C还是C++程序,运行时候的变量主要有三种分配方式:堆分配、栈分配、全局和静态内存分配。内存泄漏主要是发生在堆内存分配方式中,即“配置了内存后,所有指向该内存的指针都遗失了”,若缺乏语言这样的垃圾回收机制,这样的内存片就无法归还系统。因为内存泄漏属于程序运行中的问题,无法通过编译识别,所以只能在程序运行过程中来判别和诊断。下面将介绍几种常用的内存检测方法,每种方法均以现有的内存检测工具为分析范例,并对各种方法进行比较。

    静态分析技术

    静态分析技术就是直接分析程序的源代码或机器代码,获得一些有用的信息,而并不运行程序本身。目前有许多静态分析的工具,编译器就属于这一类,它读入源程序代码,对源程序进行词法和语法分析,进行数据类型的检查以及一些优化的分析等,以此来提高程序的质量与运行效率。这类静态的分析工具仅仅是读入程序代码进行相关的分析,而并不进行其它额外的操作,如修改源程序代码等。

    LCLink是一种通过对源代码及添加到源代码中特定格式的注释说明进行静态分析的程序理解和检错工具,的检查对象是源程序,能检查出的内存错误有内存分配释放故障、空指针的错误使用、使用未定义或己被释放的内存等程序错误。

    LCLink重点分析两类内存释放错误:

    • 试图释放某内存块,该内存块有两个或两个以上的有效指针指向它。

    • 试图释放某内存块,该内存块没有任何有效指针指向它。

    解决此类内存错误的方法是规定分配某块内存时返回的指针必须释放该内存。使用注释表示某指针是唯一指向某内存块的指针,使用注释表示被调用函数可能释放函数参数指向的内存块或创建新的指针指向该内存块。

    源代码插装技术

    为了获得被测程序的动态执行信息,需要对其进行跟踪,一般使用插装方法。所谓插装就是在保持被测程序的逻辑完整性的基础上,在被测程序的特定部位插入一段检测程序又称探针函数,通过探针的执行抛出程序的运行特征数据。基于这些特征数据分析,可以获得程序的控制流及数据流信息,进而获得逻辑覆盖等动态信息,这样就可以在被测程序执行的过程中动态地同步执行程序的检测工作。插装方法又分为源代码级程序插装和目标代码级程序插装。

    源代码插装测试必须在静态测试部分获得的被测程序的结构信息、静态数据信息、控制流信息等基础上,应用插装技术向被测程序中的适当位置植入相应类型的探针,通过运行带有探针的被测程序而获得程序运行的动态数据。源代码插装要通过运行被测程序来测定程序的各种指标,如覆盖率、时间性能、内存使用等等,实现源代码插装的关键技术是借助于插入到源程序中的监控语句来收集执行信息,以达到揭示程序内部行为和特性的目的,如图所示。

    基于源代码插装的动态测试框架分为个主要的阶段:

    • 插装交互与动态测试信息分析;

    • 插装阶段;

    • 插装库制作阶段;

    • 测试实施阶段。

    插装交互与动态测试信息分析是软件测试工具与用户交互的界面。用户通过该界面选择要进行动态测试的程序模块,拓扑产生相应的插装选择记录文件。用户还可以通过该交互界而浏览动态测试结果信息,在软件测试工具的实现上,采用可视化的方式显示这些动态信息。插装阶段实现了在被测程序中植入探针,并生成带有插装信息的源文件。在此过程中,首先将被测程序经过预处理展开为不包含宏、条件编译和头文件的文件格式。然后,按照一定的插装策略,根据前面生成的插装选择记录文件,将探针函数加载到该文件中,最后生成插装后的程序。插装库制作阶段的目的是生成插装库中的探针函数,它含有插装语句调用的函数及其函数的定义。显然,插装过程中生成的目标文件中含有探针函数的桩,而探针函数的实现恰恰在本过程完成。需要指出的是,插装库的制作过程是独立于动态测试过程之外的,可以与软件测试工具开发同步。测试实施阶段将插装过程生成的文件与插装库制作过程生成的插装静态库连接生成带有插装信息的可执行文件,选取测试用例,运行该程序,可以获得被测程序的动态跟踪信息。

    在以上四个阶段中,其中的插装交互与动态测试信息分析与测试实施阶段是测试人员的可视部分,通过这两部分,用户与系统交互,完成测试工作。而插装阶段与插装库制作阶段对测试人员是不可见的,在后台完成,对于用户而言,这两部分是完全透明的。在性能方面,采用插装方法应尽量减少插装开销。为了达到不同的统计目的如语句覆盖、分支覆盖等,应尽量减少插装次数。若能仅仅插装一次就能完成多种类型的统计,则可使插装代码得到优化。此外,应尽量减少插装代码的数量,减少插装代码的运行次数,从而达到减小插装代码运行开销的目的。特别是对于一些实时系统的测试,在这方面的要求尤为苛刻。一个运行时错误检测工具,能够自动检测一应用中大量的编程和运行时错误。通过使用源码插装和运行时指针跟踪的专利技术,在编译时,附十插入测试和分析代码,它建立一个有关程序中各种对象的数据库。然后在运行时通过检查数据值和内存引用验证对象的一致性和正确性。使用这些技术,包括变异测试技术等,一能够检查和测试用户的代码,精确定位错误的准确位置并给出详细的诊断信息。十十能够可视化实时内存操作,优化内存算法。还能执行覆盖性分析,清楚地指示那些代码己经测试过。将集成到开发环境中,能够极大地减少调试时间并有效地防止错误。检验每一次内存操作的有效性,包括静态全局和堆栈以及动态分配内存的操作。叶有两种运行模式。监护模式下用户可以快速检测代码中的错误,不需要对代码作任何插装和处理源码插装模式则进行彻底地代码检测。

    目标代码插装技术

    目标代码插装实现主要分为预处理、测试执行和结果汇总个阶段,工作流程如图所示,系统主要工作是围绕断点而进行的。在预处理阶段,首先静态分析被测程序的目标代码,查找待测程序中源代码各语句、函数入口点在目标代码中的对`应位置,然后在相应位置插入断点在测试执行阶段,启动调试进程,当被测程序执行到断点处时,响应断点信息,在相应的断点处完成相应的统计操作在结果汇总阶段,根据各断点处的统计结果,按不同的统计角度进行归并、综合得到最终的统计数据。

    被测代码预处理

    在测试预处理阶段对被测程序的目标代码进行分析,可以获得目标代码与源代码中语句、函数的对应关系。在目标代码中为相对应的源代码的每条语句及每个函数的入口点插入断点。对于第三方代码,只要其目标代码格式与下生成的目标代码格式一致,我们就可以用与分析用户代码同样的方法获取信息。获取断点的信息后,为所有的断点建立断点链表,同时建立语句及函数的信息链表,供随后的测试执行阶段存储信息。预处理流程如图所示。

    测试执行阶段

    利用OCI技术,我们把测试执行看作是一个在被测进程和检测进程间不断切换的过程。每当被测进程遇到断点,就会将自身挂起,同时发送消息唤醒检测进程,检测进程根据当前断点的地址在断点链表中查找相应节点,并查找对应的语句或函数信息,记录该语句或函数的执行次数、到达或离开的时刻,供以后统计之用。然后,将插入的断点信息去除,恢复原来的指令,转入被测进程继续执行。在转入被测进程之前,必须将上一个断点处的断点恢复上一个断点处的断点在指令运行时被去除了。具体流程如图所示。

    数据统计与结果汇总

    根据各断点处的统计结果,按不同的统计角度进行归并、综合,进行覆盖率及各种时间的计算,得到最终的统计数据。是公司出品的一种软件测试和质量保证工具,它能检测程序内存泄漏和内存访问冲突等错误。使用目标码插装技术,在编译器生成的目标码中直接插入特殊的检查指令实现对内存错误的检测。在程序的所有代码中插入这些检查逻辑,包括第三方目标码库,并且验证系统调用的接口。目标码插装技术分为链接前插装和链接后插装两种插装方法。

    使用如图所示的链接前插装法。检查插装后程序的每个内存读写动作,跟踪内存使用情况,使用类似垃圾收集器的技术来检查内存泄漏。垃圾收集机制分为两阶段垃圾检测和垃圾回收。为了不影响程序的执行速度,提供了一个可调用的垃圾检测器,使用类似于保守式垃圾收集算法,即标记一清除算法。在标记阶段,递归地从数据段、堆栈段到数据堆跟踪分析指针,并使用标准保守式方法为所有被引用的内存块做标记。在清除阶段,逐步访问数据堆,并报告已分配但程序不再引用的内存块,即程序的内存泄漏。

    检测工具

    编辑

    部分工具

    1.ccmalloc-Linux和Solaris下对C和C++程序的简单的使用内存泄漏和malloc调试库。

    2.Dmalloc-Debug Malloc Library.

    3.Electric Fence-Linux分发版中由Bruce Perens编写的malloc()调试库。

    4.Leaky-Linux下检测内存泄漏的程序。

    5.LeakTracer-Linux、Solaris和HP-UX下跟踪和分析C++程序中的内存泄漏。

    6.MEMWATCH-由Johan Lindh编写,是一个开放源代码C语言内存错误检测工具,主要是通过gcc的precessor来进行。

    7.Valgrind-Debugging and profiling Linux programs, aiming at programs written in C and C++.

    8.KCachegrind-A visualization tool for the profiling data generated by Cachegrind and Calltree.

    9.IBM Rational PurifyPlus-帮助开发人员查明C/C++、托管.NET、Java和VB6代码中的性能和可靠性错误。PurifyPlus 将内存错误和泄漏检测、应用程序性能描述、代码覆盖分析等功能组合在一个单一、完整的工具包中。

    10.ParasoftInsure++-针对C/C++应用的运行时错误自动检测工具,它能够自动监测C/C++程序,发现其中存在着的内存破坏、内存泄漏、指针错误和I/O等错误。并通过使用一系列独特的技术(SCI技术和变异测试等),彻底的检查和测试我们的代码,精确定位错误的准确位置并给出详细的诊断信息。能作为MicrosoftVisual C++的一个插件运行。

    11.Compuware DevPartner for Visual C++ BoundsChecker Suite-为C++开发者设计的运行错误检测和调试工具软件。作为Microsoft Visual Studio和C++ 6.0的一个插件运行。

    12.Electric Software GlowCode-包括内存泄漏检查,code profiler,函数调用跟踪等功能。给C++和.Net开发者提供完整的错误诊断,和运行时性能分析工具包。

    13.Compuware DevPartner Java Edition-包含Java内存检测,代码覆盖率测试,代码性能测试,线程死锁,分布式应用等几大功能模块。

    14.Quest JProbe-分析Java的内存泄漏。

    15.ej-technologies JProfiler-一个全功能的Java剖析工具,专用于分析J2SE和J2EE应用程序。它把CPU、执行绪和内存的剖析组合在一个强大的应用中。

    16.BEAJRockit-用来诊断Java内存泄漏并指出根本原因,专门针对Intel平台并得到优化,能在Intel硬件上获得最高的性能。

    展开全文
  • 内存泄露产生的原因和避免方式

    千次阅读 2018-06-22 11:05:21
    一、内存泄露如何产生?...具体主要有如下几大类:1、静态集合类引起内存泄漏:像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Ob...

    一、内存泄露如何产生?

    Java内存泄漏的根本原因是长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。

    具体主要有如下几大类:

    1、静态集合类引起内存泄漏:

    像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。

    Static Vector v = new Vector(10);
    for (int i = 1; i<100; i++)
    {
    Object o = new Object();
    v.add(o);
    o = null;
    }

    循环申请Object 对象,并将所申请的对象放入静态的Vector v 中,如果仅仅释放引用本身(o=null),那么Vector 仍然引用该对象,所以这个对象对GC 来说是不可回收的。

    2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用,此时造成内存泄露

    3、监听器

    在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。

    4、各种连接

    比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。

    5、内部类和外部模块的引用

    内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,内部类是否提供相应的操作去除外部引用。

    6、单例模式

    由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么外部对象将不能被JVM正常回收,导致内存泄漏。

    二、常见的内存泄露的处理方式

    1、集合类泄露

    集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。因此,在编写代码的时候,集合类需要有成对出现添加和删除或者清空的操作。

    2、单例造成的泄露

    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 退出时它的内存并不会被回收,这就造成泄漏了。

    还是利用静态变量的生命周期与应用的生命周期一致的特性,进行单例修改,从而避免内存泄露 方法如下

        //方式一
        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;
            }
        }
         
        //方式二 在你的 Application 中添加一个静态方法,getContext() 返回Application 的 context
    
        public class AppManager {
            private static AppManager instance;
            private Context context;
    
            private AppManager() {
                this.context = MyApplication.getContext();// 使用Application 的context
            }
    
            public static AppManager getInstance() {
                if (instance == null) {
                    instance = new AppManager();
                }
                return instance;
            }
        }

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

        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 {
                //...
            }
        }

    在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。

    正确的做法为:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,

    4、匿名内部类/异步线程


        public class MainActivity extends AppCompatActivity {
    
            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
    
                new Thread(new MyRunnable()).start();
                new MyAsyncTask(this).execute();
            }
    
            class MyAsyncTask extends AsyncTask<Void, Void, Void> {
    
                // ...
    
                public MyAsyncTask(Context context) {
                    // ...
                }
    
                @Override
                protected Void doInBackground(Void... params) {
                    // ...
                    return null;
                }
    
                @Override
                protected void onPostExecute(Void aVoid) {
                    // ...
                }
            }
    
            class MyRunnable implements Runnable {
                @Override
                public void run() {
                    // ...
                }
            }
        }
    AsyncTask和Runnable都使用了匿名内部类,那么它们将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。
    解决方法:将AsyncTask和Runnable类独立出来或者使用静态内部类,这样便可以避免内存泄漏

    5、Handle造成的内存泄露
    由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放

        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();
            }
        }
    从Android的角度
    当Android应用程序启动时,该应用程序的主线程会自动创建一个Looper对象和与之关联的MessageQueue。当主线程中实例化一个Handler对象后,它就会自动与主线程Looper的MessageQueue关联起来。所有发送到MessageQueue的Messag都会持有Handler的引用,所以Looper会据此回调Handle的handleMessage()方法来处理消息。只要MessageQueue中有未处理的Message,Looper就会不断的从中取出并交给Handler处理。另外,主线程的Looper对象会伴随该应用程序的整个生命周期。
     从Java角度
    在Java中,非静态内部类和匿名类内部类都会潜在持有它们所属的外部类的引用,但是静态内部类却不会。

    当该 Activity 被 finish() 掉时,延迟执行任务的 Message 还会继续存在于主线程中,它持有该 Activity 的 Handler 引用,所以此时 finish() 掉的 Activity 就不会被回收了从而造成内存泄漏(因 Handler 为非静态内部类,它会持有外部类的引用,在这里就是指 SampleActivity)。

    修复方法:在 Activity 中避免使用非静态内部类,比如上面我们将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity,避免直接将 Activity 作为 context 传进去,见下面代码:
    public class SampleActivity extends Activity {
    
      /**
       * Instances of static inner classes do not hold an implicit
       * reference to their outer class.
       */
      private static class MyHandler extends Handler {
        private final WeakReference<SampleActivity> mActivity;
    
        public MyHandler(SampleActivity activity) {
          mActivity = new WeakReference<SampleActivity>(activity);
        }
    
        @Override
        public void handleMessage(Message msg) {
          SampleActivity activity = mActivity.get();
          if (activity != null) {
            // ...
          }
        }
      }
    
      private final MyHandler mHandler = new MyHandler(this);
    
      /**
       * Instances of anonymous classes do not hold an implicit
       * reference to their outer class when they are "static".
       */
      private static final Runnable sRunnable = new Runnable() {
          @Override
          public void run() { /* ... */ }
      };
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        // Post a message and delay its execution for 10 minutes.
        mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
    
        // Go back to the previous Activity.
        finish();
      }
    }
    即推荐使用静态内部类 + WeakReference 这种方式

    6、避免使用static成员变量
    7、资源未关闭造成的内存泄露

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

    1)比如在Activity中register了一个BraodcastReceiver,但在Activity结束后没有unregister该BraodcastReceiver。
    2)资源性对象比如Cursor,Stream、File文件等往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。
    3)对于资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉,然后再设置为null。在我们的程序退出时一定要确保我们的资源性对象已经关闭。
    4)Bitmap对象不在使用时调用recycle()释放内存。2.3以后的bitmap应该是不需要手动recycle了,内存已经在java层了。

    8、listview没用复用contentview造成的泄露

    初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的View对象,同时ListView会将这些View对象缓存起来。当向上滚动ListView时,原先位于最上面的Item的View对象会被回收,然后被用来构造新出现在下面的Item。这个构造过程就是由getView()方法完成的,getView()的第二个形参convertView就是被缓存起来的Item的View对象(初始化时缓存中没有View对象则convertView是null)。

    构造Adapter时,没有使用缓存的convertView。
    解决方法:在构造Adapter时,使用缓存的convertView。

    9、webview 造成的内存泄露

    当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其长期占用的内存也不能被回收,从而造成内存泄露。
    解决方法:为WebView另外开启一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。

    三、避免内存泄露
    1、在涉及使用Context时,对于生命周期比Activity长的对象应该使用Application的Context。但要注意context的应用场景
    2、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏。
    3、对于不再需要使用的对象,将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null。
    4、保持对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
    5、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,如下方法可以避免内存泄漏:
    1)将内部类改为静态内部类
    2)静态内部类中使用弱引用来引用外部类的成员变量
    展开全文
  • 具体可见

    一:什么是内存泄露
    内存泄露是指:内存泄漏也称作"存储渗漏",用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。

    二:常见的内存泄露造成的原因
    1、单例造成的内存泄漏
    由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。
    示例:防止单例导致内存泄漏的实例

    // 使用了单例模式
    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;
        }
    }
    

    2、非静态内部类创建静态实例造成的内存泄漏
    例如,有时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现如下写法:

      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(mResource == null){
                mResource = new TestResource();
            }
            //...
        }
        
        class TestResource {
        //...
        }
    }   
    

    3、Handler造成的内存泄漏
    示例:创建匿名内部类的静态对象

    public class MainActivity extends AppCompatActivity {
    
        private final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // ...
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // ...
                    handler.sendEmptyMessage(0x123);
                }
            });
        }
    }
    

    1、从Android的角度
    当Android应用程序启动时,该应用程序的主线程会自动创建一个Looper对象和与之关联的MessageQueue。当主线程中实例化一个Handler对象后,它就会自动与主线程Looper的MessageQueue关联起来。所有发送到MessageQueue的Messag都会持有Handler的引用,所以Looper会据此回调Handle的handleMessage()方法来处理消息。只要MessageQueue中有未处理的Message,Looper就会不断的从中取出并交给Handler处理。另外,主线程的Looper对象会伴随该应用程序的整个生命周期。
    2、 Java角度
    在Java中,非静态内部类和匿名类内部类都会潜在持有它们所属的外部类的引用,但是静态内部类却不会。
    对上述的示例进行分析,当MainActivity结束时,未处理的消息持有handler的引用,而handler又持有它所属的外部类也就是MainActivity的引用。这条引用关系会一直保持直到消息得到处理,这样阻止了MainActivity被垃圾回收器回收,从而造成了内存泄漏。
    解决方法:将Handler类独立出来或者使用静态内部类,这样便可以避免内存泄漏。

    4、线程造成的内存泄漏
    示例:AsyncTask和Runnable

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            new Thread(new MyRunnable()).start();
            new MyAsyncTask(this).execute();
        }
    
        class MyAsyncTask extends AsyncTask<Void, Void, Void> {
    
            // ...
    
            public MyAsyncTask(Context context) {
                // ...
            }
    
            @Override
            protected Void doInBackground(Void... params) {
                // ...
                return null;
            }
    
            @Override
            protected void onPostExecute(Void aVoid) {
                // ...
            }
        }
    
        class MyRunnable implements Runnable {
            @Override
            public void run() {
                // ...
            }
        }
    }
    

    AsyncTask和Runnable都使用了匿名内部类,那么它们将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。
    解决方法:将AsyncTask和Runnable类独立出来或者使用静态内部类,这样便可以避免内存泄漏。

    5、资源未关闭造成的内存泄漏
    对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。
    1)比如在Activity中register了一个BraodcastReceiver,但在Activity结束后没有unregister该BraodcastReceiver。
    2)资源性对象比如Cursor,Stream、File文件等往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。
    3)对于资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉,然后再设置为null。在我们的程序退出时一定要确保我们的资源性对象已经关闭。
    4)Bitmap对象不在使用时调用recycle()释放内存。2.3以后的bitmap应该是不需要手动recycle了,内存已经在java层了。

    6、使用ListView时造成的内存泄漏
    初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的View对象,同时ListView会将这些View对象缓存起来。当向上滚动ListView时,原先位于最上面的Item的View对象会被回收,然后被用来构造新出现在下面的Item。这个构造过程就是由getView()方法完成的,getView()的第二个形参convertView就是被缓存起来的Item的View对象(初始化时缓存中没有View对象则convertView是null)。
    构造Adapter时,没有使用缓存的convertView。
    解决方法:在构造Adapter时,使用缓存的convertView。

    7、集合容器中的内存泄露
    我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
    解决方法:在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。

    8、WebView造成的泄露
    当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其长期占用的内存也不能被回收,从而造成内存泄露。
    解决方法:为WebView另外开启一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。

    三:如何避免内存泄漏?

    1、平常养成良好的代码书写习惯,该销毁的对象要销毁比如destory啊 广播啊 ,涉及到要用到content上下文的优先考虑全局上线文对象。

    展开全文
  • 常见的内存泄漏原因及解决方法

    万次阅读 2018-06-08 16:14:24
    (Memory Leak,内存泄漏) 为什么会产生内存泄漏? 当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就...

    (Memory Leak,内存泄漏)

    为什么会产生内存泄漏?

    当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。

    内存泄漏对程序的影响?

    内存泄漏是造成应用程序OOM的主要原因之一。我们知道Android系统为每个应用程序分配的内存是有限的,而当一个应用中产生的内存泄漏比较多时,这就难免会导致应用所需要的内存超过系统分配的内存限额,这就造成了内存溢出从而导致应用Crash。

    如何检查和分析内存泄漏?

    因为内存泄漏是在堆内存中,所以对我们来说并不是可见的。通常我们可以借助MAT、LeakCanary等工具来检测应用程序是否存在内存泄漏。
    1、MAT是一款强大的内存分析工具,功能繁多而复杂。
    2、LeakCanary则是由Square开源的一款轻量级的第三方内存泄漏检测工具,当检测到程序中产生内存泄漏时,它将以最直观的方式告诉我们哪里产生了内存泄漏和导致谁泄漏了而不能被回收。

    常见的内存泄漏及解决方法

    1、单例造成的内存泄漏

    由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。
    示例:防止单例导致内存泄漏的实例

    // 使用了单例模式
    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最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏。???

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

    例如,有时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现如下写法:

    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(mResource == null){
                mResource = new TestResource();
            }
            //...
        }
    
        class TestResource {
        //...
        }
    }
    

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

    3、Handler造成的内存泄漏

    示例:创建匿名内部类的静态对象

    public class MainActivity extends AppCompatActivity {
    
        private final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // ...
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // ...
                    handler.sendEmptyMessage(0x123);
                }
            });
        }
    }
    

    1、从Android的角度
    当Android应用程序启动时,该应用程序的主线程会自动创建一个Looper对象和与之关联的MessageQueue。当主线程中实例化一个Handler对象后,它就会自动与主线程Looper的MessageQueue关联起来。所有发送到MessageQueue的Messag都会持有Handler的引用,所以Looper会据此回调Handle的handleMessage()方法来处理消息。只要MessageQueue中有未处理的Message,Looper就会不断的从中取出并交给Handler处理。另外,主线程的Looper对象会伴随该应用程序的整个生命周期。
    2、 Java角度
    在Java中,非静态内部类和匿名类内部类都会潜在持有它们所属的外部类的引用,但是静态内部类却不会。

    对上述的示例进行分析,当MainActivity结束时,未处理的消息持有handler的引用,而handler又持有它所属的外部类也就是MainActivity的引用。这条引用关系会一直保持直到消息得到处理,这样阻止了MainActivity被垃圾回收器回收,从而造成了内存泄漏。
    解决方法:将Handler类独立出来或者使用静态内部类,这样便可以避免内存泄漏。

    4、线程造成的内存泄漏

    示例:AsyncTask和Runnable

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            new Thread(new MyRunnable()).start();
            new MyAsyncTask(this).execute();
        }
    
        class MyAsyncTask extends AsyncTask<Void, Void, Void> {
    
            // ...
    
            public MyAsyncTask(Context context) {
                // ...
            }
    
            @Override
            protected Void doInBackground(Void... params) {
                // ...
                return null;
            }
    
            @Override
            protected void onPostExecute(Void aVoid) {
                // ...
            }
        }
    
        class MyRunnable implements Runnable {
            @Override
            public void run() {
                // ...
            }
        }
    }
    

    AsyncTask和Runnable都使用了匿名内部类,那么它们将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。
    解决方法:将AsyncTask和Runnable类独立出来或者使用静态内部类,这样便可以避免内存泄漏。

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

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

    1)比如在Activity中register了一个BraodcastReceiver,但在Activity结束后没有unregister该BraodcastReceiver。
    2)资源性对象比如Cursor,Stream、File文件等往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。
    3)对于资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉,然后再设置为null。在我们的程序退出时一定要确保我们的资源性对象已经关闭。
    4)Bitmap对象不在使用时调用recycle()释放内存。2.3以后的bitmap应该是不需要手动recycle了,内存已经在java层了。

    6、使用ListView时造成的内存泄漏

    初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的View对象,同时ListView会将这些View对象缓存起来。当向上滚动ListView时,原先位于最上面的Item的View对象会被回收,然后被用来构造新出现在下面的Item。这个构造过程就是由getView()方法完成的,getView()的第二个形参convertView就是被缓存起来的Item的View对象(初始化时缓存中没有View对象则convertView是null)。

    构造Adapter时,没有使用缓存的convertView。
    解决方法:在构造Adapter时,使用缓存的convertView。

    7、集合容器中的内存泄露

    我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
    解决方法:在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。

    8、WebView造成的泄露

    当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其长期占用的内存也不能被回收,从而造成内存泄露。
    解决方法:为WebView另外开启一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。

    如何避免内存泄漏?

    1、在涉及使用Context时,对于生命周期比Activity长的对象应该使用Application的Context。凡是使用Context优先考虑Application的Context,当然它并不是万能的,对于有些地方则必须使用Activity的Context。对于Application,Service,Activity三者的Context的应用场景如下:



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

    2、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏。
    3、对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null。
    4、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
    5、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
    1)将内部类改为静态内部类
    2)静态内部类中使用弱引用来引用外部类的成员变量

    参考

    Android内存泄漏总结

    展开全文
  • java中内存泄露8种情况的总结

    万次阅读 多人点赞 2019-04-18 20:42:41
    由于java的JVM引入了垃圾...那么对于这种情况下,由于代码的实现不同就会出现很多种内存泄漏问题(让JVM误以为此对象还在引用中,无法回收,造成内存泄漏)。 1、静态集合类,如HashMap、LinkedList等等。如果这些...
  • 内存泄露是每个开发者最终都要面对的问题,它是许多问题的根源:反应迟缓,崩溃,高延迟,以及其他应用问题。 什么是内存泄露? 本质上,内存泄露可以定义为:应用程序不再需要占用内存的时候,由于某些原因,内存...
  • 内存泄漏内存溢出

    万次阅读 2018-08-22 15:40:35
    内存溢出:(out of memory)通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存...内存泄露 memory leak,是指程序在申请内存后,无法...
  • 1.什么是内存泄漏(Memory Leak)? 简单地说就是申请了一块内存...2、如何检测内存泄露第一:良好的编码习惯,尽量在涉及内存的程序段,检测出内存泄露。当程式稳定之后,在来检测内存泄露时,无疑增加了排除的困难和...
  • 最近朋友推荐了一篇关于内存溢出与内存泄漏的文章,感觉写的还不错,于是便在网上搜索了一番,对这块进行了加固,发现自己之前写的代码也存在一些内存泄漏的风险,所以弄懂内存泄漏内存溢出是很有利于我们提高代码...
  • 内存溢出和内存泄漏的区别

    万次阅读 多人点赞 2011-07-19 16:47:25
    内存溢出,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;...内存泄露,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,
  • 如何定位内存泄漏

    千次阅读 2018-07-15 22:24:00
    (1)常发性内存泄漏,发生内存泄漏的代码会被多次执行到,每次执行都会导致一块内存泄漏(2)偶发性内存泄漏(3)一次性内存泄漏,发送泄漏的代码只会被执行一次(4)隐式内存泄漏,程序在运...
  • 一、什么是内存溢出和内存泄露 内存泄漏(memory leak):是指程序在申请内存后,无法释放已申请的内存空间,导致系统无法及时回收内存并且分配给其他进程使用。通常少次数的内存无法及时回收并不会到程序造成什么...
  • C++内存泄漏及解决办法

    千次阅读 2018-10-01 21:44:51
    1.C++内存泄漏是什么? 内存泄漏指的是由于疏忽或错误造成了程序未能释放掉不再使用的内存。 2.造成的后果 性能不良,内存会耗尽 3.C++没有垃圾回收机制,我们需要关注那些类型的内存泄漏? 堆内存泄漏。在内存...
  • 闭包造成内存泄漏问题的解决办法

    千次阅读 2018-09-07 09:28:57
    由于IE的js对象和DOM对象使用不同的垃圾收集方法,因此闭包在IE中会导致内存泄露问题,也就是无法销毁驻留在内存中的元素 function closure(){ var oDiv = document.getElementById('oDiv');//oDiv用完之后一直...
  • 内存泄漏及其检测方法

    万次阅读 2019-03-07 17:39:34
    1、内存泄漏的定义   内存泄漏指的是在程序里动态申请的内存在使用完后,没有进行释放,导致这部分内存没有被系统回收,久而久之,可能导致程序内存不断增大,系统内存不足……引发一系列灾难性后果。 2、...
  • 一直以来java都占据着语言排行榜的头把交椅。这是与java的设计密不可分的,其中最令大家喜欢的...然而,情况并不是这样简单,内存泄露还是经常会在Java应用程序中出现。 下面我们将详细的学习什么是内存泄露,为什么
  • 内存泄露调试

    万次阅读 2015-08-01 17:33:08
    内存泄露调试(Memory Leak Debug) 一、概述内存泄露产生原因存在多种,但常见分类如下: 1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。 2.偶发性内存泄漏。...
  • 如果编写的c++程序出现内存泄露了,不要慌忙,你要相信任何错误都是人为造成的,只要是人为的,你一定能找到错误所在,只不过是时间的问题而已。在面对内存泄露,如果程序不是特别长的话,你可以使用人工校验,着重...
  • Toast导致Activity内存泄露的解决方法

    千次阅读 2016-11-16 11:15:13
    写了一个工具类 ToastUtils,里面实现了连续点击不重复弹出的Toast private static Toast toast; ...public static void toastInBottom(Context context, String tip) { ... toast = Toast.makeText(context,
  • 什么情况下会导致内存泄露

    千次阅读 2016-10-12 17:28:40
    下面说明几点可能导致内存泄露的原因,供大家参考。 1.对象内存过大 保存了多个好用内存过大的对象,造成内存超出限制。 2.资源释放 程序代码的问题,长期保持某些资源,如Context,Cursor,IO流的引用,资源
1 2 3 4 5 ... 20
收藏数 305,511
精华内容 122,204
关键字:

内存泄露