关于内存泄漏和内存溢出的那些事!

1、什么是内存泄漏

内存泄漏也称作"存储渗漏",用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏;也有许多其他的说法,比如:是程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢,甚至发生系统崩溃的严重后果。

2、有哪些情况会导致内存泄漏切如何解决

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

// 使用了单例模式

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类独立出来或者使用静态内部类,这样便可以避免内存泄漏。

3、线程造成的内存泄漏

示例: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所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。

3、如何检测内存泄漏

内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内bai存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。 可以使用相应的软件测试工具对软件进行检测。

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

  2. Dmalloc-Debug Malloc Library.

  3. Electric

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

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

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

  3. MEMWATCH-由Johan

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

  1. Valgrind-Debugging and profiling Linux programs, aiming at

programs written in C and C++.

  1. KCachegrind-A visualization tool for the profiling data

generated by Cachegrind and Calltree.

  1. Leak

Monitor-一个Firefox扩展,能找出跟Firefox相关的泄漏类型。

  1. IE Leak Detector

(Drip/IE Sieve)-Drip和IE Sieve leak detectors帮助网页开发员提升动态网页性能通过报告可避免的因为IE局限的内存泄漏。

  1. Windows Leaks

Detector-探测任何Win32应用程序中的任何资源泄漏(内存,句柄等),基于Win API调用钩子。

  1. SAP Memory

Analyzer-是一款开源的JAVA内存分析软件,可用于辅助查找JAVA程序的内存泄漏,能容易找到大块内存并验证谁在一直占用它,它是基于Eclipse RCP(Rich Client Platform),可以下载RCP的独立版本或者Eclipse的插件。

  1. DTrace-即动态跟踪Dynamic

Tracing,是一款开源软件,能在Unix类似平台运行,用户能够动态检测操作系统内核和用户进程,以更精确地掌握系统的资源使用状况,提高系统性能,减少支持成本,并进行有效的调节。

  1. IBM Rational PurifyPlus-帮助开发人员查明C/C++、托管.NET、Java和VB6代码中的性能和可靠性错误。PurifyPlus

将内存错误和泄漏检测、应用程序性能描述、代码覆盖分析等功能组合在一个单一、完整的工具包中。

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

Visual C++的一个插件运行。

  1. Compuware DevPartner for Visual C++ BoundsChecker

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

  1. Electric Software GlowCode-包括内存泄漏检查,code

profiler,函数调用跟踪等功能。给C++和.Net开发者提供完整的错误诊断,和运行时性能分析工具包。

  1. Compuware DevPartner Java

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

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

  2. ej-technologies JProfiler-一个全功能的Java剖析工具,专用于分析J2SE和J2EE应用程序。它把CPU、执行绪和内存的剖析组合在一个强大的应用中。JProfiler可提供许多IDE整合和应用服务器整合用途。JProfiler直觉式的GUI让你可以找到效能瓶颈、抓出内存泄漏、并解决执行绪的问题。4.3.2注册码:A-G666#76114F-1olm9mv1i5uuly#0126

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

  4. SciTech Software AB .NET Memory

Profiler-找到内存泄漏并优化内存使用针对C#,VB.Net,或其它.Net程序。

  1. YourKit .NET & Java Profiler-业界领先的Java和.NET程序性能分析工具。

  2. AutomatedQA AQTime-AutomatedQA的获奖产品performance profiling和memory

debugging工具集的下一代替换产品,支持Microsoft, Borland, Intel, Compaq 和 GNU编译器。可以为.NET和Windows程序生成全面细致的报告,从而帮助您轻松隔离并排除代码中含有的性能问题和内存/资源泄露问题。支持.Net 1.0,1.1,2.0,3.0和Windows 32/64位应用程序。

  1. JavaScript Memory Leak Detector-微软全球产品开发欧洲团队(Global Product

Development- Europe team, GPDE) 发布的一款调试工具,用来探测JavaScript代码中的内存泄漏,运行为IE系列的一个插件。

4、Java得基本数据类型和占用字节(附加)

一、Java基本数据类型

基本数据类型有8种:byte、short、int、long、float、double、boolean、char

分为4类:整数型、浮点型、布尔型、字符型。

整数型:byte、short、int、long

浮点型:float、double

布尔型:boolean

字符型:char

二、各数据类型所占字节大小

计算机的基本单位:bit .  一个bit代表一个0或1

byte/boolean:1byte = 8bit     1个字节是8个bit

short/char:2byte

int/float:4byte

long/double:8byte

三、各个类型最大存储数据量和数据范围

  •             字节  大小     最大存储数据量    数据范围
    
  • byte --------1----8位------2^8-1------- -28~28-1

  • short -------2----16位-----2^16-1------ -216~216-1

  • int ---------4----32位-----2^32-1------ -232~232-1

  • long --------8----64位-----2^64-1------ -264~264-1

  • boolean -----1----8位------(实际只占用一位),只有true和false两个取值,有时内存一个bit位

  • char --------2----16位----- 存储Unicode码,用单引号赋值,表示单个字符

  • float -------4----32位----- 直接赋值时必须在数字后加上f-F------- 3.4e-45~1.4e38

  • double ------8----64位----- 赋值时可以加上d或D也可以不加------- 4.9e-324~-1.8e308

5、什么是内存溢出和解决办法

内存溢出
(1) 避免上面所说的导致内存溢出原因
(2) 在使用内存之前检查指针是否为NULL。假设指针p 是函数的參数,那么在函数的入口处用assert(p!=NULL)进行检查。假设是用malloc 或new 来申请内存,应该用if(p==NULL)或if(p!=NULL)进行防错处理。

内存泄露
(1) 良好的编码习惯,尽量在涉及内存的程序段,检測出内存泄露。
(2) 重载 new 和 delete。将分配的内存以链表的形式自行管理,使用完成之后从链表中删除,程序结束时可检查改链表,当中记录了内存泄露的文件
(3) 使用智能指针。
(4) 一些常见的工具软件BoundsChecker,它主要定位程序运行时期发生的各种错误。
(5) 调试运行DEBUG版程序,运用以下技术:CRT(C run-time libraries)、运行时函数调用堆栈、内存泄漏时提示的内存分配序号(集成开发环境OUTPUT窗口),综合分析内存泄漏的原因,排除内存泄漏。

重点排查以下几点:

1.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
2.检查代码中是否有死循环或递归调用。
3.检查是否有大循环重复产生新对象实体。
4.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
5.检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

猜你喜欢

转载自blog.csdn.net/dongrimaomaoyu/article/details/113360252