Android 的内存泄露和内存限制

转载自html

https://blog.csdn.net/goodlixueyong/article/details/40716779 java

https://blog.csdn.net/vshuang/article/details/39647167android

一、Android 进程管理&内存

Android主要应用在嵌入式设备当中,而嵌入式设备因为一些众所周知的条件限制,一般都不会有很高的配置,特别是内存是比较有限的。若是咱们编写的代 码当中有太多的对内存使用不当的地方,不免会使得咱们的设备运行缓慢,甚至是死机。为了可以使得Android应用程序安全且快速的运行,Android 的每一个应用程序都会使用一个专有的Dalvik虚拟机实例来运行,它是由Zygote服务进程演变过来的,也就是说每一个应用程序都是在属于本身的进程中运行的。一方面,若是程序在运行过程当中出现了内存泄漏的问题,仅仅会使得本身的进程被杀掉,而不会影响其余进程(若是是system_process 等系统进程出问题的话,则会引发系统重启)。另外一方面Android为不一样类型的进程分配了不一样的内存使用上限,若是应用进程使用的内存超过了这个上限, 则会被系统视为内存泄漏,从而被杀掉。

同时,Android会为每一个应用程序分配一个单独的LINUX用户。Android会尽可能保留一个正在运行进程,只在内存资源出现不足时,Android会尝试中止一些进程从而释放足够的资源给其余新的进程使用, 也能保证用户正在访问的当前进程有足够的资源去及时地响应用户的事件。Android会根据进程中运行的组件类别以及组件的状态来判断该进程的重要性,Android会首先中止那些不重要的进程。按照重要性从高到低一共有五个级别就是咱们常说的:前台进程、可见进程、服务进程、后台进程、空进程。

二、单个应用可用的最大内存

Android设备出厂之后,java虚拟机对单个应用的最大内存分配就肯定下来了,超出这个值就会OOM。这个属性值是定义在/system/build.prop文件中的
dalvik.vm.heapstartsize=8m
它表示堆分配的初始大小,它会影响到整个系统对RAM的使用程度,和第一次使用应用时的流畅程度。
它值越小,系统ram消耗越慢,但一些较大应用一开始不够用,须要调用gc和堆调整策略,致使应用反应较慢。它值越大,这个值越大系统ram消耗越快,可是应用更流畅。

dalvik.vm.heapgrowthlimit=64m // 单个应用可用最大内存
主要对应的是这个值,它表示单个进程内存被限定在64m,即程序运行过程当中实际只能使用64m内存,超出就会报OOM。(仅仅针对dalvik堆,不包括native堆)

dalvik.vm.heapsize=384m//heapsize参数表示单个进程可用的最大内存,但若是存在heapgrowthlimit参数,则以heapgrowthlimit为准.
heapsize表示不受控状况下的极限堆,表示单个虚拟机或单个进程可用的最大内存。而android上的应用是带有独立虚拟机的,也就是每开一个应用就会打开一个独立的虚拟机(这样设计就会在单个程序崩溃的状况下不会致使整个系统的崩溃)。
注意:在设置了heapgrowthlimit的状况下,单个进程可用最大内存为heapgrowthlimit值。在android开发中,若是要使用大堆,须要在manifest中指定android:largeHeap为true,这样dvm heap最大可达heapsize。

不一样设备,这些个值能够不同。通常地,厂家针对设备的配置状况都会适当的修改/system/build.prop文件来调高这个值。随着设备硬件性能的不断提高,从最先的16M限制(G1手机)到后来的24m,32m,64m等,都遵循Android框架对每一个应用的最小内存大小限制,参考http://source.android.com/compatibility/downloads.html 3.7节。

经过代码查看每一个进程可用的最大内存,即heapgrowthlimit值:
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int memClass = activityManager.getMemoryClass();//64,以m为单位
 

上面的几个参数是与虚拟机的内存分配相关的,虚拟机的内存分配过程是下面这样的:程序员

 

1  首先判断一下须要申请的size是否是过大,若是申请的size超过了堆的最大限制,则转入步骤6数据库

2  尝试分配,若是成功则返回,失败则转入步骤3数组

3  判断是否gc正在进行垃圾回收,若是正在进行则等待回收完成以后,尝试分配。若是成功则返回,失败则转入步骤4缓存

4  本身启动gc进行垃圾回收,这里gcForMalloc的参数是false。因此不会回收软引用,回收完成后尝试分配,若是成功则返回,失败则转入步骤5安全

5  调用dvmHeapSourceAllocAndGrow尝试分配,这个函数会扩张堆。因此heap startup的时候能够给一个比较小的初始堆,实在不够用再调用它进行扩张网络

6  进入回收软引用阶段,这里gcForMalloc的参数是ture,因此须要回收软引用。而后调用dvmHeapSourceAllocAndGrow尝试分配,若是失败则抛出OOM。框架

三、为何会内存泄露(Memory Leak)?

  android经过android虚拟机来管理内存,程序员只管申请内存建立对象,建立完再也不须要关心怎么释放对象内存,一切由虚拟机帮你搞定,然而虚拟机回收对象是有条件的。这里简单叙述下java内存管理机制,java虚拟机维护着一张当前对象关系的object tree,当GC发生时,虚拟机会从GC Roots 开始去扫描当前的对象树,发现经过任何reference chain(引用链)没法访问某个对象的时候,该对象即被回收。名词GC Roots正是分析这一过程的起点,例如JVM本身确保了对象的可到达性(那么JVM就是GC Roots),因此GC Roots就是这样在内存中保持对象可到达性的,一旦不可到达,即被回收。一般GC Roots是一个在current thread(当前线程)的call stack(调用栈)上的对象(例如方法参数和局部变量),或者是线程自身或者是system class loader(系统类加载器)加载的类以及native code(本地代码)保留的活动对象。因此GC Roots是分析对象为什么还存活于内存中的利器。知道了什么样的对象GC才会回收后,再来学习下对象引用都包含哪些吧。
Java中包含4种对象引用:
强引用: 一般咱们编写的代码都是Strong Ref,eg :Person person = new Person("sunny");无论系统资源有多紧张,强引用的对象都绝对不会被回收,即便他之后再也不用到。
软引用:只要有足够的内存,就一直保持对象。通常可用来实现缓存,经过java.lang.r.efSoftReference类实现。内存很是紧张的时候会被回收,其余时候不会被回收,因此在使用以前须要判空,从而判断当前时候已经被回收了。
弱引用:经过WeakReference类实现,eg : WeakReference p = new WeakReference(new Person("Rain"));无论内存是否足够,系统垃圾回收时一定会回收。
虚引用:不能单独使用,主要是用于追踪对象被垃圾回收的状态。经过PhantomReference类和引用队列ReferenceQueue类联合使用实现。
咱们可能还须要了解shallow size、retained size概念,简单来讲,Shallow size就是对象自己占用内存的大小,不包含对其余对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。在32位系统上,对象头占用8字节,int占用4字节,无论成员变量(对象或数组)是否引用了其余对象(实例)或者赋值为null它始终占用4字节。故此,对于String对象实例来讲,它有三个int成员(34=12字节)、一个char[]成员(14=4字节)以及一个对象头(8字节),总共34 +14+8=24字节。根据这一原则,对String a=”rosen jiang”来讲,实例a的shallow size也是24字节。Retained size是该对象本身的shallow size,加上只能从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC以后所能回收到内存的总和。为了更好的理解retained size,咱们来看个例子。

    图1
假设内存中对象之间的引用关系能够当作图1的方式,从图中能够看到 GC正是reference chain的起点。从obj1入手,上图中蓝色节点表明仅仅只有经过obj1才能直接或间接访问的对象。由于能够经过GC Roots访问,因此左图的obj3不是蓝色节点;而在右图倒是蓝色,由于它已经被包含在retained集合内。因此对于左图,obj1的retained size是obj一、obj二、obj4的shallow size总和;右图的retained size是obj一、obj二、obj三、obj4的shallow size总和。
相信了有以上的这些基础概念,咱们应该对java内存管理有了一个初步的了解。
 
为何会内存泄露呢,根本缘由就是一个永远不会被使用的对象,由于一些引用没有断开,没有知足GC条件,致使不会被回收,这就形成了内存泄露。好比在Activity中注册了一个广播接收器,可是在页面关闭的时候进行unRegister,就会出现内存溢出的现象。若是咱们的java运行好久,而这种内存泄露不断的发生,最后就没内存可用了,最终就是咱们看到的OOM错误。虽然android的内存泄露作到了应用程序级别的泄露(android中的每一个应用程序都是独立运行在单独进程中的,每一个应用进程都由虚拟机指定了一个内存上限值,一旦内存占用值超过这个上限值,就会发生oom错误,进程被强制kill掉,kill掉的进程内存会被系统回收),可是对于一名开发工程师,绝对不能放过任何的内存泄露。

四、JAVA中的内存泄露

1.静态集合类像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,全部的对象Object也不能被释放,由于他们也将一直被Vector等应用着。

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

在这个例子中,代码栈中存在Vector 对象的引用 v 和 Object 对象的引用 o 。在 For 循环中,咱们不断的生成新的对象,而后将其添加到 Vector 对象中,以后将 o 引用置空。问题是当 o 引用被置空后,若是发生 GC,咱们建立的 Object 对象是否可以被 GC 回收呢?答案是否认的。由于, GC 在跟踪代码栈中的引用时,会发现 v 引用,而继续往下跟踪,就会发现 v 引用指向的内存空间中又存在指向 Object 对象的引用。也就是说尽管o 引用已经被置空,可是 Object 对象仍然存在其余的引用,是能够被访问到的,因此 GC 没法将其释放掉。若是在此循环以后, Object 对象对程序已经没有任何做用,那么咱们就认为此 Java 程序发生了内存泄漏。

2.各类链接,数据库链接,网络链接,IO链接等没有显示调用close关闭,不被GC回收致使内存泄露。

3.监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能致使内存泄露。

五、为何会发生OOM(Out Of Memory)?

OOM:即OutOfMemoery,顾名思义就是指内存溢出了。以前咱们知道Android的应用程序所能申请的最大内存都是有限的,OOM是指APP向系统申请内存的请求超过了应用所能有的最大阀值的内存,系统没法再分配多余的空间,就会形成OOM error。在Android平台下,除了以前所说的持续发生了内存泄漏(Memory Leak),累积到必定程度致使OOM的状况之外,也有一次性申请不少内存,好比说一次建立大的数组或者是载入大的文件如图片的时候。实际中不少状况就是出如今图片不当处理加载的时候。

六、常见的MemoryLeak分析 

后来看到了更多的MemoryLeak相关的知识,有了更多的实践经验,
就此小小总结了一下,见  Android 内存优化 (防Memory Leak)
相关文章
相关标签/搜索