JVM内存管理概述与android内存泄露分析

 一.内存划分php

将内存划分为六大部分,分别是PC寄存器、JAVA虚拟机栈、JAVA堆、方法区、运行时常量池以及本地方法栈.
一、PC寄存器(线程独有):全称是程序计数寄存器,它记载着每个线程当前运行的JAVA方法的地址,
若是是当前执行的是本地方法,则程序计数器会是一个空地址。它的做用就是用来支持多线程,线程的阻塞、恢复、
挂起等一系列操做,直观的想象一下,要是没有记住每一个线程当前运行的位置,又如何恢复呢。依据这一点,
每个线程都有一个PC寄存器,也就是说PC寄存器是线程独有的。html

二、JAVA虚拟机栈(线程独有):JAVA虚拟机栈是在建立线程的同时建立的,用于存储栈帧,
JAVA虚拟机栈也是线程独有的。java

三、JAVA堆(全局共享):这一部分是JAVA内存中最重要的一部分,之因此说是最重要的一部分,
并非由于它的重要性,而是指做为开发人员最应该关注的一部分。它随着JAVA虚拟机的启动建立,
储存着全部对象实例以及数组对象,并且内置了“自动内存管理系统”,也就是咱们常说的垃圾搜集器(GC)。
JAVA堆中的内存释放是不受开发人员控制的,彻底由JAVA虚拟机一手操办。对于JAVA虚拟机如何实现垃圾搜集器,
JAVA虚拟机规范没有明确的规定,也正因如此,咱们平时使用的JAVA虚拟机中提供了许多种垃圾搜集器,
它们采用不一样的算法以及实现方式,已知足多方面的性能需求。android

四、方法区(全局共享):方法区也是堆的一个组成部分,它主要存储的是运行时常量池、字段信息、方法信息、
构造方法与普通函数的字节码内容以及一些特殊方法。它与JAVA堆的区别除了存储的信息与JAVA堆不同以外,
最大的区别就是这一部分JAVA虚拟机规范不强制要求实现自动内存管理系统(GC)。git

五、本地方法栈(线程独有):本地方法栈是一个传统的栈,它用来支持native方法的执行。
若是JAVA虚拟机是使用的其它语言实现指令集解释器的时候,也会用到本地方法栈。若是前面这两种都未发生,
也就是说若是JAVA虚拟机不依赖于本地方法栈,并且JAVA虚拟机也不支持native方法,则不须要本地方法栈。
而若是须要的话,则本地方法栈也是随每个线程的启动而建立的。程序员

上面五个内存区域,除了PC寄存器以外,其他四个通常状况下,都要求JAVA虚拟机实现提供给客户调节大小的参数,
也就是咱们经常使用的Xms、Xmx等等。github

Java 内存分配策略

Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区。web

静态存储区(方法区):主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,而且在程序整个运行期间都存在。 
栈区 :当方法被执行时,方法体内的局部变量都在栈上建立,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。由于栈内存分配运算内置于处理器的指令集中,效率很高,可是分配的内存容量有限。 
堆区 : 又称动态内存分配,一般就是指在程序运行时直接 new 出来的内存。这部份内存在不使用时将会由 Java 垃圾回收器来负责回收。 
栈与堆的区别:算法

在方法体内定义的(局部变量)一些基本类型的变量和对象的引用变量都是在方法的栈内存中分配的。当在一段方法块中定义一个变量时,Java 就会在栈中为该变量分配内存空间,当超过该变量的做用域后,该变量也就无效了,分配给它的内存空间也将被释放掉,该内存空间能够被从新使用。编程

堆内存用来存放全部由 new 建立的对象(包括该对象其中的全部成员变量)和数组。在堆中分配的内存,将由 Java 垃圾回收器来自动管理。在堆中产生了一个数组或者对象后,还能够在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,这个特殊的变量就是咱们上面说的引用变量。咱们能够经过这个引用变量来访问堆中的对象或者数组。

 

四种引用类型的介绍

  1. 强引用(StrongReference):JVM 宁肯抛出 OOM ,也不会让 GC 回收具备强引用的对象;

  2. 软引用(SoftReference):只有在内存空间不足时,才会被回的对象;

  3. 弱引用(WeakReference):在 GC 时,一旦发现了只具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存;

  4. 虚引用(PhantomReference):任什么时候候均可以被GC回收,当垃圾回收器准备回收一个对象时,若是发现它还有虚引用,就会在回收对象的内存以前,把这个虚引用加入到与之关联的引用队列中。程序能够经过判断引用队列中是否存在该对象的虚引用,来了解这个对象是否将要被回收。能够用来做为GC回收Object的标志。

咱们常说的内存泄漏是指new出来的Object没法被GC回收,即为强引用:  

什么是Java中的内存泄露

对于C++来讲,内存泄漏就是new出来的对象没有delete,俗称野指针;对于Java来讲,就是new出来的Object 放在Heap上没法被GC回收;  

在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特色,首先,这些对象是可达的,即在有向图中,存在通路能够与其相连;其次,这些对象是无用的,即程序之后不会再使用这些对象。若是对象知足这两个条件,这些对象就能够断定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,而后却不可达,因为C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,所以程序员不须要考虑这部分的内存泄露。

经过分析,咱们得知,对于C++,程序员须要本身管理边和顶点,而对于Java程序员只须要管理边就能够了(不须要管理顶点的释放)。经过这种方式,Java提升了编程的效率。

所以,经过以上分析,咱们知道在Java中也有内存泄漏,但范围比C++要小一些。由于Java从语言上保证,任何对象都是可达的,全部的不可达对象都由GC管理。

对于程序员来讲,GC基本是透明的,不可见的。虽然,咱们只有几个函数能够访问GC,例如运行GC的函数System.gc(),可是根据Java语言规范定义, 该函数不保证JVM的垃圾收集器必定会执行。由于,不一样的JVM实现者可能使用不一样的算法管理GC。一般,GC的线程的优先级别较低。JVM调用GC的策略也有不少种,有的是内存使用到达必定程度时,GC才开始工做,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但一般来讲,咱们不须要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不但愿GC忽然中断应用程序执行而进行垃圾回收,那么咱们须要调整GC的参数,让GC可以经过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性。

一样给出一个 Java 内存泄漏的典型例子,

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

在这个例子中,咱们循环申请Object对象,并将所申请的对象放入一个 Vector 中,若是咱们仅仅释放引用自己,那么 Vector 仍然引用该对象,因此这个对象对 GC 来讲是不可回收的。所以,若是对象加入到Vector 后,还必须从 Vector 中删除,最简单的方法就是将 Vector 对象设置为 null。

 

Android中常见的内存泄漏汇总 

什么是内存溢出:(Out Of Memory,简称 OOM),通俗理解就是内存不够,即内存占用超出内存的空间大小;基本上会形成程序奔溃。解决:找到报错的代码并解决。
什么是内存泄露:内存泄漏(Memory Leak),简单理解就是内存使用完毕以后本该垃圾回收却未被回收。(占着茅坑不拉屎)--内存没法回收又没起做用。

    

推理!!!
1.看对象的大小;
2.深刻使用MAT进行对象细节的分析
3.先后对比。
怀疑某一段代码会形成内存溢出。能够先注册掉该段代码生成一个xxx.hroph文件,而后在解开这段代码再生成一个yyy.hroph文件
经过MAT工具对比他们的内存消耗状况---甚至能够精确到每个对象消耗内存的状况。

 

 在 Android 中,泄露 Context 对象的问题尤为严重,特别像 Activity 这样的 Context 对象会引用大量很占用内存的对象,若是 Context 对象发生了内存泄漏,那它所引用的全部对象都被泄漏了。Activity 是很是重量级的对象,因此咱们应该极力避免妨碍系统对其进行回收,然而实际状况是有多种方式会无心间就泄露了Activity 对象。

case 0.静态变量形成的内存泄漏

 最简单的泄漏 Activity 就是在 Activity 类中定义一个 static 变量,并将其指向一个运行中的 Activity 实例。若是在 Activity 的生命周期结束以前,没有清除这个引用,那它就会泄漏。因为 Activity 的类对象是静态的,一旦加载,就会在 APP 运行时一直常驻内存,若是类对象不卸载,其静态成员就不会被垃圾回收。

尽可能避免使用 static 成员变量:
这里修复的方法是:
不要在类初始时初始化静态成员。能够考虑lazy初始化。

case 1. 单例形成的内存泄露

单例的静态特性致使其生命周期同应用同样长

另外一种相似的状况是对常常启动的 Activity 实现一个单例模式,让其常驻内存可使它可以快速恢复状态。

    如咱们有一个建立起来很是耗时的 View,在同一个 Activity 不一样的生命周期中都保持不变呢,就为它实现一个单例模式。一旦 View 被加载到界面中,它就会持有 Context 的强引用,也就是咱们的 Activity 对象。

    因为咱们是经过一个静态成员引用了这个 View,因此咱们也就引用了 Activity,所以 Activity 就发生了泄漏。因此必定不要把加载的 View 赋值给静态变量,若是你真的须要,那必定要确保在 Activity 销毁以前将其从 View 层级中移除。

解决方案:

  1. 将该属性的引用方式改成弱引用;

  2. 若是传入Context,使用ApplicationContext;

case 2. InnerClass匿名内部类

咱们常常在 Activity 内部定义一个内部类,这样作能够增长封装性和可读性。可是若是当咱们建立了一个内部类的对象,并经过静态变量持有了 Activity 的引用,那也会可能发生 Activity 泄漏。

在Java中,非静态内部类 和 匿名类 都会潜在的引用它们所属的外部类,可是,静态内部类却不会。若是这个非静态内部类实例作了一些耗时的操做,就会形成外围对象不会被回收,从而致使内存泄漏。

解决方案:

  1. 将内部类变成静态内部类;

  2. 静态内部类中使用弱引用来引用外部类的成员变量; 

  3. 若是有强引用Activity中的属性,则将该属性的引用方式改成弱引用;

  4. 在业务容许的状况下,当Activity执行onDestory时,结束这些耗时任务;

case 3. 线程形成的内存泄漏

 在 Activity 内定义了一个匿名的 AsyncTask 对象,就有可能发生内存泄漏。若是 Activity 被销毁以后 AsyncTask 仍然在执行,那就会阻止垃圾回收器回收Activity 对象,进而致使内存泄漏,直到执行结束才能回收 Activity。

    一样的,使用 Thread 和 TimerTask 也可能致使 Activity 泄漏。只要它们是经过匿名类建立的,尽管它们在单独的线程被执行,它们也会持有对 Activity 的强引用,进而致使内存泄漏。

在Android里面线程最容易形成内存泄露。线程产生内存泄露的主要缘由在于线程生命周期的不可控
2.线程问题的改进方式主要有:
   1)将线程的内部类,改成静态内部类。
   2)在程序中尽可能采用弱引用保存Context。

case 4. Activity Context 的不正确使用

在Android应用程序中一般可使用两种Context对象:Activity和Application。当类或方法须要Context对象的时候常见的作法是使用第一个做为Context参数。这样就意味着View对象对整个Activity保持引用,所以也就保持对Activty的全部的引用。

假设一个场景,当应用程序有个比较大的Bitmap类型的图片,每次旋转是都从新加载图片所用的时间较多。为了提升屏幕旋转是Activity的建立速度,最简单的方法时将这个Bitmap对象使用Static修饰。 当一个Drawable绑定在View上,实际上这个View对象就会成为这份Drawable的一个Callback成员变量。而静态变量的生命周期要长于Activity。致使了当旋转屏幕时,Activity没法被回收,而形成内存泄露。

解决方案:

  1. 使用ApplicationContext代替ActivityContext,由于ApplicationContext会随着应用程序的存在而存在,而不依赖于activity的生命周期;

  2. 对Context的引用不要超过它自己的生命周期,慎重的对Context使用“static”关键字。Context里若是有线程,必定要在onDestroy()里及时停掉。

case 5. Handler引发的内存泄漏

定义一个匿名的 Runnable 对象并将其提交到 Handler 上也可能致使 Activity 泄漏。Runnable 对象间接地引用了定义它的 Activity 对象,而它会被提交到Handler 的 MessageQueue 中,若是它在 Activity 销毁时尚未被处理,就会致使 Activity 泄漏。

当Handler中有延迟的的任务或是等待执行的任务队列过长,因为消息持有对Handler的引用,而Handler又持有对其外部类的潜在引用,这条引用关系会一直保持到消息获得处理,而致使了Activity没法被垃圾回收器回收,而致使了内存泄露。

修复方法:在 Activity 中避免使用非静态内部类,好比上面咱们将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。同时经过弱引用的方式引入 Activity,避免直接将 Activity 做为 context 传进去,见下面代码:

Handler 的持有的引用对象最好使用弱引用,资源释放时也能够清空 Handler 里面的消息。好比在 Activity onStop 或者 onDestroy 的时候,取消掉该 Handler 对象的 Message和 Runnable.
综述,即推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。

解决方案:

  1. 能够把Handler类放在单独的类文件中,或者使用静态内部类即可以免泄露;

  2. 若是想在Handler内部去调用所在的Activity,那么能够在handler内部使用弱引用的方式去指向所在Activity.使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。

case 6. 注册监听器的泄漏(资源未关闭形成的内存泄漏)

如系统服务能够经过 context.getSystemService 获取,它们负责执行某些后台任务,或者为硬件访问提供接口。若是 Context 对象想要在服务内部的事件发生时被通知,那就须要把本身注册到服务的监听器中。然而,这会让服务持有 Activity 的引用,若是开发者忘记在 Activity 销毁时取消注册,也会致使 Activity泄漏

系统服务能够经过Context.getSystemService 获取,它们负责执行某些后台任务,或者为硬件访问提供接口。若是Context 对象想要在服务内部的事件发生时被通知,那就须要把本身注册到服务的监听器中。然而,这会让服务持有Activity 的引用,若是在Activity onDestory时没有释放掉引用就会内存泄漏。

解决方案:

  1. 使用ApplicationContext代替ActivityContext;

  2. 在Activity执行onDestory时,调用反注册;

case 7. Cursor,Stream没有close,View没有recyle

资源性对象好比(Cursor,File文件等)每每都用了一些缓冲,咱们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不只存在于 java虚拟机内,还存在于java虚拟机外。若是咱们仅仅是把它的引用设置为null,而不关闭它们,每每会形成内存泄漏。由于有些资源性对象,好比SQLiteCursor(在析构函数finalize(),若是咱们没有关闭它,它本身会调close()关闭),若是咱们没有关闭它,系统在回收它时也会关闭它,可是这样的效率过低了。所以对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,而后才置为null. 在咱们的程序退出时必定要确保咱们的资源性对象已经关闭。

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

Solution:

调用onRecycled()

case 8. 集合中对象没清理形成的内存泄漏

咱们一般把一些对象的引用加入到了集合容器(好比ArrayList)中,当咱们不须要该对象时,并无把它的引用从集合中清理掉,这样这个集合就会愈来愈大。若是这个集合是static的话,那状况就更严重了。
因此要在退出程序以前,将集合里的东西clear,而后置为null,再退出程序。

解决方案:

在Activity退出以前,将集合里的东西clear,而后置为null,再退出程序。

Solution

private List<EmotionPanelInfo> data;    
public void onDestory() {        
   if (data != null) {        data.clear();        data = null;    } }

case 9. WebView形成的泄露

当咱们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,不然其占用的内存长期也不能被回收,从而形成内存泄露。

解决方案:

为webView开启另一个进程,经过AIDL与主线程进行通讯,WebView所在的进程能够根据业务的须要选择合适的时机进行销毁,从而达到内存的完整释放。

case 10. 构造Adapter时,没有使用缓存的ConvertView

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

 

case 11.动画

在属性动画中有一类无限循环动画,若是在Activity中播放这类动画而且在onDestroy中去中止动画,那么这个动画将会一直播放下去,这时候Activity会被View所持有,从而致使Activity没法被释放。解决此类问题则是须要早Activity中onDestroy去去调用objectAnimator.cancel()来中止动画

case 12.第三方库使用不当

对于EventBus,RxJava等一些第三开源框架的使用,如果在Activity销毁以前没有进行解除订阅将会致使内存泄漏。

 

综上所述,要避免内存泄露或者内存溢出,主要要遵循如下几点:

第一:不要为Context长期保存引用(要引用Context就要使得引用对象和它自己的生命周期保持一致,即对activity的引用应该控制在activity的生命周期以内)。

第二:若是要使用到Context,尽可能使用ApplicationContext去代替Context,由于ApplicationContext的生命周期较长,引用状况下不会形成内存泄露问题

第三:在你不控制对象的生命周期的状况下避免在你的Activity中使用static变量。尽可能使用WeakReference去代替一个static。

第四:垃圾回收器并不保证能准确回收内存,这样在使用本身须要的内容时,主要生命周期和及时释放掉不须要的对象。尽可能在Activity的生命周期结束时,在onDestroy中把咱们作引用的其余对象作资源释放,好比:cursor.close()。如清空对图片等资源有直接引用或者间接引用的数组(使用array.clear();array = null);

第五:尽可能不要在Activity中使用非静态内部类,由于非静态内部类会隐式持有外部类实例的引用。若是使用静态内部类,将外部实例引用做为弱引用持有。
(静态的内部类不会持有外部类的一个隐式引用)

在 Java 的实现过程当中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,好比使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁建立谁释放的原则。
正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。

 

程序出现中止运行状态或者致命崩溃现象可能以下缘由致使:

1.在while死循环里面或软件操做很是频繁的代码块中进行new对象产生强引用对象,
致使jvm不能及时回收内存,从而产生内存消耗暴增,让软件出现中止运行的致命崩溃现象

2.代码报错未捕捉异常形成

3.程序在主线程耗时过长,或者在广播中的耗时超过6s

 

new出来的对象不用时回收原则;

1.在哪建立就在哪及时释放,
2.谁引用,谁就负责释放
能作到C/C++对于程序的“谁建立,谁释放”原则,那咱们对于内存的把握,并不比Java或Android自己的GC机制差,并且更好的控制内存,能使咱们的手机运行得更流畅

java没有绝对的强制垃圾回收的方法,不过能够这样去作:
1. 对于再也不引用的对象,及时把它的引用赋为null。 obj = null;
2. 若是内存确实很紧张,调用System.gc() 方法来建议垃圾回收器开始回收垃圾。

 

参考文章

以上部分图片、实例代码和文段都摘自或参考如下文章 : 
IBM : 
Java的内存泄漏

Android Design Patterns : 
How to Leak a Context: Handlers & Inner Classes

伯乐在线团队: 
Android性能优化之常见的内存泄漏

我厂同窗 : 
Dalvik虚拟机 Finalize 方法执行分析

腾讯bugly : 
内存泄露从入门到精通三部曲之基础知识篇

LeakCanary : 
LeakCanary 中文使用说明 
LeakCanary: 让内存泄露无所遁形

https://github.com/square/leakcanary

相关文章
相关标签/搜索