java四种引用及在LeakCanery中应用
java 四种引用
Java4种引用的级别由高到低依次为:
StrongReference > SoftReference > WeakReference > PhantomReference
1. StrongReference
String tag = new String("T");
此处的 tag 引用就称之为强引用。而强引用有以下特征:
1. 强引用可以直接访问目标对象。
2. 强引用所指向的对象在任何时候都不会被系统回收。
3. 强引用可能导致内存泄漏。
我们要讨论的其它三种Reference较之于强引用而言都属于“弱引用”,也就是他们所引用的对象只要没有强引用,就会根据条件被JVM的垃圾回收器所回收,它们被回收的时机以及用法各不相同。下面分别来进行讨论。
2. SoftReference
软引用有以下特征:
1. 软引用使用 get() 方法取得对象的强引用从而访问目标对象。
2. 软引用所指向的对象按照 JVM 的使用情况(Heap 内存是否临近阈值)来决定是否回收。
3. 软引用可以避免 Heap 内存不足所导致的异常。
当垃圾回收器决定对其回收时,会先清空它的 SoftReference,也就是说 SoftReference 的 get() 方法将会返回 null,然后再调用对象的 finalize() 方法,并在下一轮 GC 中对其真正进行回收。
3. WeakReference
WeakReference 是弱于 SoftReference 的引用类型。弱引用的特性和基本与软引用相似,区别就在于弱引用所指向的对象只要进行系统垃圾回收,不管内存使用情况如何,永远对其进行回收(get() 方法返回 null)。
弱引用有以下特征:
1. 弱引用使用 get() 方法取得对象的强引用从而访问目标对象。
2. 一旦系统内存回收,无论内存是否紧张,弱引用指向的对象都会被回收。
3. 弱引用也可以避免 Heap 内存不足所导致的异常。
4. PhantomReference(虚引用)
PhantomReference 是所有“弱引用”中最弱的引用类型。不同于软引用和弱引用,虚引用无法通过get()方法来取得目标对象的强引用从而使用目标对象,观察源码可以发现 get() 被重写为永远返回 null。
虚引用有以下特征:
虚引用永远无法使用 get() 方法取得对象的强引用从而访问目标对象。
虚引用所指向的对象在被系统内存回收前,虚引用自身会被放入 ReferenceQueue 对象中从而跟踪对象垃圾回收。
虚引用不会根据内存情况自动回收目标对象。
虚引用必须和引用队列(ReferenceQueue)联合使用
Reference与ReferenceQueue 使用demo
定义一个对象Brain
public class Brain {
public int mIndex;
// 占用较多内存,当系统内存不足时,会自动进行回收
private byte []mem;
public Brain(int index) {
mIndex = index;
mem = new byte[1024 * 1024];
}
@Override
protected void finalize() throws Throwable {
super.finalize();
LogUtils.e("Brain", "finalize + index=" + mIndex);
}
}
创建Reference并添加到RefrenceQueue中
// 定义几个成员变量
ReferenceQueue<Brain> mWeakQueue = new ReferenceQueue<>();
ReferenceQueue<Brain> mPhQueue = new ReferenceQueue<>();
List<WeakReference<Brain>> mWeakList = new ArrayList<>();
List<PhantomReference<Brain>> mPhList = new ArrayList<>();
// 开启守护线程用于检测ReferenceQue中是否有对象被添加
private void startDemoThread() {
Thread threadWeak = new Thread(() -> {
try {
int cnt = 0;
WeakReference<Brain> k;
// remove 方法为阻塞式的, 而poll方法不是
while((k = (WeakReference) mWeakQueue.remove()) != null) {
LogUtils.e(TAG, "回收了WeakReference指向对象, : cnt=" + (cnt++) + " wf=" + k);
}
} catch(InterruptedException e) {
//结束循环
}
}, "MainActivityWeak");
threadWeak.setDaemon(true);
threadWeak.start();
Thread threadPh = new Thread(() -> {
try {
int cnt = 0;
PhantomReference<Brain> k;
while((k = (PhantomReference) mPhQueue.remove()) != null) {
LogUtils.e(TAG, "回收了PhantomReference指向对象, cnt=" + (cnt++) + " pr=" + k);
}
} catch(InterruptedException e) {
//结束循环
}
}, "MainActivityPhantom");
threadPh.setDaemon(true);
threadPh.start();
}
// 注意创建的Reference对象需要暂存起来(实测中,如果Reference被回收是不会被添加到ReferenceQueue中的)
private void test() {
for (int i=0; i<1000; i++) {
Brain src1 = new Brain(i);
Brain src2 = new Brain(100000 + i);
// 将Reference注册到RefrenceQueue中
WeakReference<Brain> weakReference = new WeakReference<Brain>(src1, mWeakQueue);
mWeakList.add(weakReference);
//将Reference注册到RefrenceQueue中
PhantomReference<Brain> phantomReference = new PhantomReference<>(src2, mPhQueue);
mPhList.add(phantomReference);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果打印:
2019-01-29 19:22:27.499 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=0 wf=java.lang.ref.WeakReference@e1f904c
2019-01-29 19:22:27.499 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=1 wf=java.lang.ref.WeakReference@82fc895
2019-01-29 19:22:27.500 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=2 wf=java.lang.ref.WeakReference@3b3fdaa
2019-01-29 19:22:27.500 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=3 wf=java.lang.ref.WeakReference@668fd9b
2019-01-29 19:22:27.501 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=0
2019-01-29 19:22:27.501 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100000
2019-01-29 19:22:27.502 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=4 wf=java.lang.ref.WeakReference@8db6538
2019-01-29 19:22:27.502 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=5 wf=java.lang.ref.WeakReference@f915911
2019-01-29 19:22:27.503 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=1
2019-01-29 19:22:27.503 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100001
2019-01-29 19:22:27.504 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=2
2019-01-29 19:22:27.505 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100002
2019-01-29 19:22:27.507 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=3
2019-01-29 19:22:27.507 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100003
2019-01-29 19:22:27.507 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=4
2019-01-29 19:22:27.508 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100004
2019-01-29 19:22:27.508 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=5
2019-01-29 19:22:27.509 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100005
2019-01-29 19:22:27.629 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=0 pr=null
2019-01-29 19:22:27.629 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=1 pr=null
2019-01-29 19:22:27.629 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=6 wf=java.lang.ref.WeakReference@e2c4a76
2019-01-29 19:22:27.630 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=2 pr=null
2019-01-29 19:22:27.630 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=7 wf=java.lang.ref.WeakReference@4cfd877
2019-01-29 19:22:27.630 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=3 pr=null
2019-01-29 19:22:27.630 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=4 pr=null
2019-01-29 19:22:27.630 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=8 wf=java.lang.ref.WeakReference@37d9ce4
2019-01-29 19:22:27.630 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=5 pr=null
2019-01-29 19:22:27.630 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=9 wf=java.lang.ref.WeakReference@ea1754d
结果分析:
- 当对象被回收后,持有他的引用WeakReference/PhantomReference会被放入ReferenceQueue中
- WeakReference在原始对象回收之前被放入ReferenceQueue中,而PhantomReference是在回收之后放入ReferenceQueue中
WeakReference在Leakcanery中的应用
LeakCanery是Android检测内存泄漏的工具,可以检测到Activity/Fragment存在的内存泄漏。
检测原理:
- 在Application中注册监听所有Activity生命周期的listener,registerActivityLifecycleCallbacks。
//ActivityRefWatcher 中的代码
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
- 当Activity的onDestroy被调用时,生成一个uuid,标记这个Activity的WeakReference。
- 创建一个弱引用,并与一个跟踪所有activit回收的ReferenceQueue相关联。(放入一个map中,key : uuid, value:weakReference)
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
具体的watch执行如下:
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
ensureGoneAsync执行如下:
// watchExecutor 在一定时间后检查被注册的WeakReference有没有被添加到ReferenceQueue中
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
- 在onDestry被调用后若干秒执行如下操作:到ReferenceQueue中去取这个Activity,如果能够取到说明这个Activity被正常回收了。如果无法回收,触发GC,再去RerenceQueue中取如果还是无法取到,说明Activity没有被系统回收,可能存在内存泄漏。
真正核心的代码如下:
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 如果ReferenceQue中有activity的弱引用,则将retainedKeys中的uuid移除
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// 如果activity对应的uuid已经被移除,说明activity已经被回收,无内存泄漏
if (gone(reference)) {
return DONE;
}
// 触发gc,进行垃圾回收
gcTrigger.runGc();
removeWeaklyReachableReferences();
// 如果uuid还没有被移除,说明activiy存在内存泄漏,需要dump内存,进行分析
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
heapdumpListener.analyze(heapDump);
}
return DONE;
}
HeapDump dump内存和分析的过程这里就不细说。
WeakReference在ThreadLocal中应用
我们知道ThreadLocal是用来存放当前线程值的一个类,也是线程同步的一个工具。
顺便介绍下它的原理,首先我们得知道Thread对象中有一个ThreadLocalMap的成员变量,存放当前线程的所有ThreadLocal值。
该map中存放的数据类型如下:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
当我们调用new ThreadLocal().set(5)时, 首先会获取到当前线程的ThreadLocalMap对象,然后通过自身作为key值到获取value。
如果这里没有使用弱引用,那么ThreadLocal对象很可能无法释放。
WeakReference在Handler中使用防止内存泄漏
熟悉Handler消息机制的都知道,mHandler会作为成员变量保存在发送的消息msg中,即msg持有mHandler的引用,而mHandler是Activity的非静态内部类实例,即mHandler持有Activity的引用,那么我们就可以理解为msg间接持有Activity的引用。msg被发送后先放到消息队列MessageQueue中,然后等待Looper的轮询处理(MessageQueue和Looper都是与线程相关联的,MessageQueue是Looper引用的成员变量,而Looper是保存在ThreadLocal中的)。那么当Activity退出后,msg可能仍然存在于消息对列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露。
下面使用弱应用方式创建handler:
public class MainActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new MyHandler(this);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private static class MyHandler extends Handler {
private WeakReference<MainActivity> activityWeakReference;
public MyHandler(MainActivity activity) {
activityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = activityWeakReference.get();
if (activity != null) {
if (msg.what == 1) {
// 做相应逻辑
}
}
}
}
}
java四种引用及在LeakCanery中应用的更多相关文章
- Java四种引用包括强引用,软引用,弱引用,虚引用。
Java四种引用包括强引用,软引用,弱引用,虚引用. 强引用: 只要引用存在,垃圾回收器永远不会回收Object obj = new Object();//可直接通过obj取得对应的对象 如obj.e ...
- 不可访问内存 Java四种引用包括强引用,软引用,弱引用,虚引用
小结: 1.不可访问内存是指一组没有任何可访问指针指向的由计算机程序进行动态分配的内存块. 2.垃圾收集器能决定是否一个对象还是可访问的:任何被确定不可访问的对象将会被释放. https://zh.w ...
- java四种引用与回调函数
JAVA四种引用 java对象的引用包括: 强引用 软引用 弱引用 虚引用 Java中提供这四种引用类型主要有两个目的: 第一是可以让程序员通过代码的方式决定某些对象的生命周期: 第二是有利于JVM进 ...
- Java四种引用--《深入理解Java虚拟机》学习笔记及个人理解(四)
Java四种引用--<深入理解Java虚拟机>学习笔记及个人理解(四) 书上P65. StrongReference(强引用) 类似Object obj = new Object() 这类 ...
- JAVA四种引用方式
JAVA四种引用方式: java.lang.ref: 强引用(直接变量赋值) 软引用(SoftReference): 只有在要发生OOM错误之前才会回收掉老的软引用对象,应用场景主要防止内存溢出.(缓 ...
- Java四种引用
Java中提供了一个Reference抽象类,此类定义所有引用对象共有的操作,与垃圾收集器密切配合实现的.主要是为了决定某些对象的生命周期,有利于JVM进行垃圾回收.而继承此类的有四种引用,分别是St ...
- 【转】JAVA四种引用(强引用,弱引用,软引用,虚引用)
转自:http://www.cnblogs.com/gudi/p/6403953.html 1.强引用(StrongReference) 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器 ...
- Java 四种引用介绍及使用场景
强引用-FinalReference 介绍: 强引用是平常中使用最多的引用,强引用在程序内存不足(OOM)的时候也不会被回收,使用方式: String str = new String("s ...
- Java基础:Java的四种引用
在Java基础:java虚拟机(JVM)中,我们提到了Java的四种引用.包括:强引用,软引用,弱引用,虚引用.这篇博客将详细的讲解一下这四种引用. 1. 强引用 2. 软引用 3. 弱引用 4. 虚 ...
随机推荐
- windows无法执行 git reset head^版本回退操作的正确打开方式
^是cmd.exe的escape字符,属于特殊字符,命令里要用到文字 ^ 时必须用双引号把它夹起来,因此只要如下就可以正确执行: git reset head"^"或者git re ...
- Flink 灵魂两百问,这谁顶得住?
Flink 学习 https://github.com/zhisheng17/flink-learning 麻烦路过的各位亲给这个项目点个 star,太不易了,写了这么多,算是对我坚持下来的一种鼓励吧 ...
- C#汽车租赁系统 完整版
Truck.cs类 //卡车类 public class Truck : Vehicle1 { //重载 public int Load { get; set; } //构造函数 public T ...
- web设计_6_图片/标题/说明文字布局
这个web中常见的单元布局,最好的布局方式就是利用float布局. 其中有个很关键的问题是需要清浮动.子集浮动是无法撑开父级的高度. 目前较完善的清浮动解决方案:在浮动的父级上添加.clear,达到清 ...
- Integrating Thymeleaf with Spring
这个是基于注解的配置方式,基于配置文件的http://www.cnblogs.com/honger/p/6875148.html 一.整体结构图 二.web.xml文件,这里使用了注解的方式 < ...
- Ubuntu启动Apache
划重点:最重要的是找到一个文件--------startup.sh 如果你是按照百度上的方法下载的Apache2,那么就按照百度的方式来 但是异曲同工 如果你是自己在网上下载的Apache到的磁盘 找 ...
- vue动态表单
项目需求,需要根据后台接口返回数据,动态添加表单内容 说明:此组件基于Ant Design of Vue 目前支持六种表单控件:文本输入框(TextInput).文本域输入框(TextArea).下拉 ...
- postman使用pre-request script计算md5
接口加了验签逻辑,具体是md5(salt+时间戳).被某君吐槽说测试不方便啊能不能先关掉.其实没有必要打开又关闭验签功能,postman的pre-request script功能完全可以模拟客户端加密 ...
- jenkins未授权访问漏洞
jenkins未授权访问漏洞 一.漏洞描述 未授权访问管理控制台,可以通过脚本命令行执行系统命令.通过该漏洞,可以后台管理服务,通过脚本命令行功能执行系统命令,如反弹shell,wget写webshe ...
- 2019最新最全Java开发面试常见问题答案总结
2019最新最全Java开发面试常见问题答案总结 马上准备9月份出去面试Java开发,自己学习丢西瓜捡芝麻,学了的都忘了,所以有机会自己做个学习笔记,摘录自各个博文以及总结. 1.JAVA面向对象的特 ...