从弱引用到内存泄漏的分析

1、为什么会有引用的几种分类?

因为Java中对引用的定义就是: 某个数据数值上存储的是某个对象的引用地址,那么就认为该数据对象引用着某个对象。所以,对象存在着2种关系: 要么引用着别的对象,要么被别的对象所引用。

由于 Java 是自动管理虚拟机内存的,垃圾回收机制自动监控虚拟机内所有对象的情况,当某个对象被GC Root 不再引用时,它即被认为是不可达对象,系统就会会把它回收。

基于对象的引用与回收关系,Java 定义了几种类型:即强引用、软引用、弱引用以及虚引用。

(1)强引用

除了使用WeakReference \ SoftReference 等创建出来的对象之外,其他大部分创建出来的对象都是强引用;对于强引用,哪怕是它成为不可达对象(不再被任何其他对象所引用,也不再引用其他对象),java 虚拟机即使是发生OOM(OutOfMemeoryError)也不会回收它;

(2)软引用

使用SoftReference类声明的对象就是软引用,当它成为不可达对象时,一旦Java 虚拟机内存不足,无法为新对象分配对象时,GC 回收线程就会回收这些可回收的软引用;注意的是,回收的前提有2个: 不可达 and 内存不足;

软引用的一般用处是 内存缓存;当内存充足时,缓存能提供比IO更快的速度;内存不足时,为了系统稳定,缓存即可以舍弃掉,换用IO访问;

(3)弱引用

使用 WeakReference 类声明的对象就是弱引用对象,当它成为不可达对象时,在下一次GC回收时,不管当前系统是否内存不足(注意:与软引用的区别,软引用必须是内存不足才可以回收),Java 虚拟机都会立刻回收弱引用对象;

弱引用的设计目的是用来存储一些不再使用时可以舍弃掉的数据,比如更通用的缓存;

(4)虚引用

虚引用是四种引用中引用强度最低的,它只要被垃圾回收器发现,就会立刻被回收,不管当前内存何种状态。在java中,使用PhantomReference声明一个虚引用对象,它可以被用来监听GC回收的频率,GC回收越频繁说明该系统内存使用越紧张;

2、Android 中被使用弱引用的典型例子

Java 数据结构: WeakHashMap,  ThreadLocal

LeakCanary 开源库

LeakCanary 的原理可以分为三板斧: 监听 - 检测 -  分析;

(1)监听: 

使用  LeakCanary.install(Application) 初始化,建立对app的所有activity / fragment等进行监听,监听的原理是注册了一个activity生命周期监听 ActivityLifecycleCallbacks,在所有activity销毁时使用该activity对象生成一个弱引用;RefWatcher 是具体的监听者;

(2)检测

如何检测是否发生了内存泄漏呢? RefWatcher它里面有个引用队列 ReferenceQueue,当加入一个监听对象时,RefWatcher为其创建一软引用对象,KeyedWeakReference,其中会传入刚才的ReferenceQueue;当GC 检测到一个对象可被回收时,就会把该对象放到传入的ReferenceQueue里;

双重检测回收

监听开始后,在异步线程里,会先判断一次是否被回收,若还没有,则主动调用GC一次,当GC发生后再去检查弱引用对象里的引用队列是否有引用对象,若有,证明发生了回收;若没有,证明该对象还没被回收掉,可能是发生了泄漏;

(3)分析

Leakcanary 提供了把内存泄漏路径显示出来的功能,在检测到有内存泄漏的那一刻,就使用HeapDumper 这个工具类得到一个后缀是.hprof的内存快照文件 ,然后交给HeapAnalyzerService 到单独的进程进行分析;为什么是单独的进程呢? 因为堆内存是按照进程分配内存的,分析的动作如果在原来的进程,肯定会对原来的用户进程造成影响,影响内存使用,所以要放在单独的进程里,互不影响;具体分析的工作在旧版本是通过MAT 的API来实现,但最新的版本是交给square自己开源的工程库HAHA 来实现;

分析代码截图

如何分析得到具体引用路径呢? 

1、由于监听时生成的引用名字是通过时间戳来生成key,可以保证每个引用都是独一无二;通过该key来找到内存快照中刚刚泄漏的引用;

2、HeapAnalyzer 会计算该引用被引用的强引用到GC Root的最短引用路径;

3、把泄漏的堆栈路径通过DisplayLeakService 前台service发送到用户进程显示出来;

你可能感兴趣的:(从弱引用到内存泄漏的分析)