腾讯Bugly特约做者: 姚潮生java
首先以一个内存泄露实例来开始本节基础概念的内容。程序员
能够看出ImageUtil这个工具类是一个单例,并引用了activity的context。算法
试想这个场景,应用起来之后,转屏。转屏之后,旧MainActivity会destroy,新MainActivity会重建,致使单例ImageUtil从新getInstance。很不幸的是,因为instance已经不是空的了,因此ImageUtil不会重建,还持有以前的Context,也就是以前的那个MainActivity实例的context,所以会形成两个问题:编程
功能问题:使用ImageUitl访问context相关内容时可能会发生异常(由于当前context并非当前activity的context);windows
内存泄露:旧context被生命周期更长的静态变量持有而致使activity没法释放形成泄漏!(所以静态变量是很容易所以内存泄露的!)数组
使用工具能够看到ImageUtil引用了MainActivity致使MainActivity驻留内存发生泄漏。缓存
备注:本系列部分概念和例子引用来自网络。网络
首先咱们来了解程序运行时,所需内存的分配策略:函数
按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的、栈式的、和堆式的,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、堆区和栈区。他们的功能不一样,对他们使用方式也就不一样。工具
静态存储区(方法区):内存在程序编译的时候就已经分配好,这块内存在程序整个运行期间都存在。它主要存放静态数据、全局static数据和常量。
栈区:在执行函数时,函数内局部变量的存储单元均可以在栈上建立,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,可是分配的内存容量有限。
堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员本身负责在适当的时候用free或delete释放内存(Java则依赖垃圾回收器)。动态内存的生存期能够由咱们决定,若是咱们不释放内存,程序将在最后才释放掉动态内存。 可是,良好的编程习惯是:若是某动态内存再也不使用,须要将其释放掉。
接下来咱们集中说下堆和栈的区别:
在函数中(说明是局部变量)定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的做用域后,java会自动释放掉为该变量分配的内存空间,该内存空间能够马上被另做他用。
堆内存用于存放全部由new建立的对象(内容包括该对象其中的全部成员变量)和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还能够在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,之后就能够在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量至关于为数组或者对象起的一个别名,或者代号。
堆是不连续的内存区域(由于系统是用链表来存储空闲内存地址,天然不是连续的),堆大小受限于计算机系统中有效的虚拟内存(32bit系统理论上是4G),因此堆的空间比较灵活,比较大。栈是一块连续的内存区域,大小是操做系统预约好的,windows下栈大小是2M(也有是1M,在编译时肯定,VC中可设置)。
对于堆,频繁的new/delete会形成大量内存碎片,使程序效率下降。对于栈,它是先进后出的队列,进出一一对应,不产生碎片,运行效率稳定高。
举一个关于变量存储位置的实例2:
结论:
局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。——由于它们属于方法中的变量,生命周期随方法而结束。
成员变量所有存储与堆中(包括基本数据类型,引用和引用的对象实体)——由于它们属于类,类对象终究是要被new出来使用的。
回到咱们的问题:内存泄露须要关注的是什么?
咱们这里说的内存泄露,是针对,也只针对堆内存,他们存放的就是引用指向的对象实体。
为了判断Java中是否有内存泄露,咱们首先必须了解Java是如何管理(堆)内存的。Java的内存管理就是对象的分配和释放问题。在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不须要经过调用函数来释放内存,但它只能回收无用而且再也不被其它对象引用的那些对象所占用的空间。
Java的内存垃圾回收机制是从程序的主要运行对象(如静态对象/寄存器/栈上指向的堆内存对象等)开始检查引用链,当遍历一遍后获得上述这些没法回收的对象和他们所引用的对象链,组成没法回收的对象集合,而其余孤立对象(集)就做为垃圾回收。GC为了可以正确释放对象,必须监控每个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都须要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象再也不被引用。
在Java中,这些无用的对象都由GC负责回收,所以程序员不须要考虑这部分的内存泄露。虽然,咱们有几个函数能够访问GC,例如运行GC的函数System.gc(),可是根据Java语言规范定义,该函数不保证JVM的垃圾收集器必定会执行。由于不一样的JVM实现者可能使用不一样的算法管理GC。一般GC的线程的优先级别较低。JVM调用GC的策略也有不少种,有的是内存使用到达必定程度时,GC才开始工做,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但一般来讲,咱们不须要关心这些。
至此,咱们来看看Java中须要被回收的垃圾:
{ Person p1 = new Person(); …… }
引用句柄p1的做用域是从定义到“}”处,执行完这对大括号中的全部代码后,产生的Person对象就会变成垃圾,由于引用这个对象的句柄p1已超过其做用域,p1失效,在栈中被销毁,所以堆上的Person对象再也不被任何句柄引用了。 所以person变为垃圾,会被回收。
从上面的例子和解释,能够看到一个很关键的词:引用。
通俗的讲,经过A能调用并访问到B,那就说明A持有 B 的引用,或A就是B的引用,B的引用计数 +1。
好比 Person p1 = new Person();经过P1能操做Person对象,所以P1是Person的引用;
好比类O中有一个成员变量是I类对象,所以咱们可使用o.i的方式来访问I类对象的成员,所以o持有一个i对象的引用。
GC过程与对象的引用类型是严重相关的,咱们来看看Java对引用的分类Strong reference, SoftReference, WeakReference, PhatomReference
讲多一步,这里的软引用/弱引用通常是作什么的呢?
在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大并且声明周期较长的对象时候,能够尽可能应用软引用和弱引用技术。
软/弱引用能够和一个引用队列(ReferenceQueue)联合使用,若是软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列能够得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。
假设咱们的应用会用到大量的默认图片,好比应用中有默认的头像,默认游戏图标等等,这些图片不少地方会用到。若是每次都去读取图片,因为读取文件须要硬件操做,速度较慢,会致使性能较低。因此咱们考虑将图片缓存起来,须要的时候直接从内存中读取。可是,因为图片占用内存空间比较大,缓存不少图片须要不少的内存,就可能比较容易发生OutOfMemory异常。这时,咱们能够考虑使用软/弱引用技术来避免这个问题发生。如下就是高速缓冲器的雏形:
首先定义一个HashMap,保存软引用对象。
private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
再来定义一个方法,保存Bitmap的软引用到HashMap。
public class CacheBySoftRef { // 首先定义一个HashMap,保存软引用对象。 private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>(); // 再来定义一个方法,保存Bitmap的软引用到HashMap。 public void addBitmapToCache(String path) { // 强引用的Bitmap对象 Bitmap bitmap = BitmapFactory.decodeFile(path); // 软引用的Bitmap对象 SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap); // 添加该对象到Map中使其缓存 imageCache.put(path, softBitmap); } // 获取的时候,能够经过SoftReference的get()方法获得Bitmap对象。 public Bitmap getBitmapByPath(String path) { // 从缓存中取软引用的Bitmap对象 SoftReference<Bitmap> softBitmap = imageCache.get(path); // 判断是否存在软引用 if (softBitmap == null) { return null; } // 经过软引用取出Bitmap对象,若是因为内存不足Bitmap被回收,将取得空 ,若是未被回收,则可重复使用,提升速度。 Bitmap bitmap = softBitmap.get(); return bitmap; } }
使用软引用之后,在OutOfMemory异常发生以前,这些缓存的图片资源的内存空间能够被释放掉的,从而避免内存达到上限,避免Crash发生。
若是只是想避免OutOfMemory异常的发生,则可使用软引用。若是对于应用的性能更在乎,想尽快回收一些占用内存比较大的对象,则可使用弱引用。
另外能够根据对象是否常用来判断选择软引用仍是弱引用。若是该对象可能会常用的,就尽可能用软引用。若是该对象不被使用的可能性更大些,就能够用弱引用。
回到咱们的问题,为何内存会泄露?
堆内存中的长生命周期的对象持有短生命周期对象的强/软引用,尽管短生命周期对象已经再也不须要,可是由于长生命周期对象持有它的引用而致使不能被回收,这就是Java中内存泄露的根本缘由。
Bugly 是腾讯内部产品质量监控平台的外发版本,其主要功能是App发布之后,对用户侧发生的Crash以及卡顿现象进行监控并上报,让开发同窗能够第一时间了解到App的质量状况,及时机型修改。目前腾讯内部全部的产品,均在使用其进行线上产品的崩溃监控。