观察者模式Java内存管理进阶篇——如何避免内存泄漏
一、什么叫观察者模式
观察者模式是常用的设计模式之一;例如在下载文件时,我们可能会更新图标动画,另外在别的控件显示当前下载进度,下载完成后要对文件进行处理,可能这些处理过程都是不同的业务模块,这些模块的生命周期不一样。
简单来说观察者模式可以理解为对多个回调实体对象的管理;
二、简单案例
下面我们通过一个简单的例子来讲解如何设计观察者模式:
本案例是新建一个线程可以设定定时器,在时间到时发送当前的时间,最后把时间反馈给注册者。
首先我们定义回调接口
public interface ObserverCallback {public void onTime(String textTime);
}
然后在时间触发处管理回调实体
package com.penny.javademo;import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;public class Observer implements Runnable {private static final String TAG = Observer.class.getSimpleName();private volatile boolean running = false;private long interval = 1000;private List<ObserverCallback> callbacks = new CopyOnWriteArrayList<>();public Observer(long intervalMillsec) {if (interval < 1) {throw new IllegalArgumentException("interval must be larger than 0. interval: " + interval);}this.interval = intervalMillsec;}public void addCallback(ObserverCallback cb) {if (null == cb) {return;}synchronized (this) { // 防止异步线程同时find然后插入boolean find = false;for (ObserverCallback temp : callbacks) {if (cb == temp) {find = true;break;}}if (!find) {callbacks.add(cb);}}}public void removeCallback(ObserverCallback cb) {synchronized (this) {boolean find = false;for (ObserverCallback temp : callbacks) {if (cb == temp) {find = true;break;}}if (find) {callbacks.remove(cb);}}}public void initEvent() {synchronized (this) {if (! running) {new Thread(this).start();}}}public void terminate() {synchronized (this) {this.running = false;}}@Overridepublic void run() {boolean localRun = running;synchronized (this) {this.running = true;localRun = running;}DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");while (localRun) {// 处理回掉事件for (ObserverCallback cb : callbacks) {cb.onTime(df.format(Calendar.getInstance().getTime()));}try {Thread.sleep(interval);} catch (InterruptedException e) {e.printStackTrace();}synchronized (this) {localRun = running;}}synchronized (this) {this.running = false;}}
}
回调管理者,时间触发为定时发送当前时间;
@Test
public void testObserver() {final String tag = "testObserver";Observer o = new Observer(2000);o.addCallback(new ObserverCallback() {@Overridepublic void onTime(String textTime) {LogUtils.debug(tag, "111 textTime: " + textTime);}});o.addCallback(new ObserverCallback() {@Overridepublic void onTime(String textTime) {LogUtils.debug(tag, "222 textTime: " + textTime);}});o.initEvent();try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}LogUtils.debug(TAG, "testDemo finish.");
}
输出结果:
D/testObserver 111 textTime: 2019-03-16 15:11:06
D/testObserver 222 textTime: 2019-03-16 15:11:06
D/testObserver 111 textTime: 2019-03-16 15:11:08
D/testObserver 222 textTime: 2019-03-16 15:11:08
D/testObserver 111 textTime: 2019-03-16 15:11:10
D/testObserver 222 textTime: 2019-03-16 15:11:10
D/testObserver 111 textTime: 2019-03-16 15:11:12
D/testObserver 222 textTime: 2019-03-16 15:11:12
D/testObserver 111 textTime: 2019-03-16 15:11:14
D/testObserver 222 textTime: 2019-03-16 15:11:14
D/ObserverTest testDemo finish.Process finished with exit code 0
上述代码虽然功能完善,但是存在一个问题,假如有观察对象注册了callback然后忘记注销,由于定时器一直持有该对象那么就会导致内存泄漏。
三、WeakReference加持
对于内存泄漏这个问题我们可以通过Java软引用来解决。
1. 首先用一个List来存储所有回调callback,该list用软引用WeakReference方式持有callback;
2. 用第二个List在存储强制类型的回调callback,该list直接持有callback;
3. 注册者在向定时器注册回调时可以设置注册引用类型,如果为强引用,那么步骤1、2的List都加入该对象;如果为软引用,那么只有步骤1的List会加入该对象。
4. 定时器触发事件时只需要遍历步骤1的Lit;
5. 注销某个注册者时需要遍历步骤1、2的callback,然后删除之。
package com.penny.javademo;import java.lang.ref.WeakReference;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;public class ObserverWeakRef3 implements Runnable {private static final String TAG = ObserverWeakRef3.class.getSimpleName();private volatile boolean running = false;private long interval = 1000;private List<WeakReference<ObserverCallback>> callbacks = new CopyOnWriteArrayList<>();private List<ObserverCallback> strongList = new CopyOnWriteArrayList<>();public ObserverWeakRef3(long intervalMillsec) {if (interval < 1) {throw new IllegalArgumentException("interval must be larger than 0. interval: " + interval);}this.interval = intervalMillsec;}public void addCallback(ObserverCallback cb, boolean isWeakRef) {if (null == cb) {return;}synchronized (this) { // 防止异步线程同时find然后插入boolean find = false;for (WeakReference<ObserverCallback> ref : callbacks) {if (cb == ref.get()) {find = true;break;}}if (!find) {callbacks.add(new WeakReference<>(cb));if (!isWeakRef) {strongList.add(cb);}}}}public void removeCallback(ObserverCallback cb) {synchronized (this) {boolean find = false;List<WeakReference<ObserverCallback>> delList = new ArrayList<>();ObserverCallback localCallback = null;for (WeakReference<ObserverCallback> ref : callbacks) {localCallback = ref.get();if (null == localCallback || cb == localCallback) {delList.add(ref);}if (cb == localCallback) {find = true;}}if (find) {strongList.remove(cb);}for (WeakReference<ObserverCallback> temp : delList) {callbacks.remove(temp);}delList.clear();}}public void initEvent() {synchronized (this) {if (! running) {new Thread(this).start();}}}public void terminate() {synchronized (this) {this.running = false;}}@Overridepublic void run() {boolean localRun = running;synchronized (this) {this.running = true;localRun = running;}ObserverCallback localCallback;DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");while (localRun) {// 处理回掉事件for (WeakReference<ObserverCallback> cb : callbacks) {localCallback = cb.get();if (null != localCallback) {localCallback.onTime(df.format(Calendar.getInstance().getTime()));} else {LogUtils.debug(TAG, "localCallback: null");}}try {Thread.sleep(interval);} catch (InterruptedException e) {e.printStackTrace();}synchronized (this) {localRun = running;}}synchronized (this) {this.running = false;}}
}
测试代码:
@Test
public void testObserverWeakRef3() {final String tag = "testObserverWeakRef3";ObserverWeakRef2 o = new ObserverWeakRef2(2000);for (int i = 0; i < 10; i++) {final String text = String.format("%d%d%d", i, i, i);o.addCallback(new ObserverCallback() {byte[] buf = new byte[1024 * 1024 * 10]; // 增大空间,诱发虚拟机回收内存@Overridepublic void onTime(String textTime) {LogUtils.debug(tag, text + " textTime: " + textTime);}}, i > 4);}o.initEvent();try {Thread.sleep(6000);System.gc(); // 手动触发gc,最终执行还是靠虚拟机的策略Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}LogUtils.debug(TAG, "testDemo finish.");
}
输出结果。
D/testObserverWeakRef3 000 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 111 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 222 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 333 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 444 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 555 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 666 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 777 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 888 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 999 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 000 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 111 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 222 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 333 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 444 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 555 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 666 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 777 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 888 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 999 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 000 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 111 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 222 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 333 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 444 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 555 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 666 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 777 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 888 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 999 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 000 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3 111 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3 222 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3 333 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3 444 textTime: 2019-03-16 15:37:01
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/testObserverWeakRef3 999 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3 000 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3 111 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3 222 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3 333 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3 444 textTime: 2019-03-16 15:37:03
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/testObserverWeakRef3 999 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3 000 textTime: 2019-03-16 15:37:05
D/testObserverWeakRef3 111 textTime: 2019-03-16 15:37:05
D/testObserverWeakRef3 222 textTime: 2019-03-16 15:37:05
D/testObserverWeakRef3 333 textTime: 2019-03-16 15:37:05
D/testObserverWeakRef3 444 textTime: 2019-03-16 15:37:05
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/testObserverWeakRef3 999 textTime: 2019-03-16 15:37:05
D/ObserverTest testDemo finish.Process finished with exit code 0
通过打印输出可以看到,通过WeakReference软引用的callback在 2019-03-16 15:37:01时就被系统回收了,因此找不到对应的callback。证明用软引用是有效的,可以避免内存泄漏;
四、总结
当然,最重要的还是调用者本身要懂得对注册者的生命周期类型,如果把原本弱周期的注册者放到定时器是使用强引用方式注册,然后又不注销,那么也会导致内存泄漏。对于这种情况我只能说——我也很无奈啊!!!
观察者模式Java内存管理进阶篇——如何避免内存泄漏相关推荐
- 【C语言】动态内存管理 [进阶篇_ 复习专用]
- Java面试题-进阶篇(2022.4最新汇总)
Java面试题-进阶篇 1. 基础篇 1.1 基本数据类型和包装类 1.2 Double转Bigdecimal可能会出现哪些问题?怎么解决? 1.3 equals 与 == 的区别? 1.4 Java ...
- 读懂 JVM 内存管理这篇就够了
读懂 JVM 内存管理这篇就够了 JVM 的内存结构 程序计数器 作用 概述 PC寄存器的常见问题 虚拟机栈 栈中可能出现的异常 栈的存储单位 栈运行原理 栈帧的内部结构 局部变量表 槽 Slot 操 ...
- java内存管理的一些基础,内存溢出的解决方案
Java的内存组成: Java的内存主要有两种:栈内存(stack)和堆内存(heap) 栈内存的优势是存取速度快,在栈中存放的变量都是在编译期就可确定其值.生命周期的,栈内存最大的一个特点 ...
- Linux内存管理宏观篇(七)虚拟内存
Linux内存管理宏观篇(七)虚拟内存 前面知道了物理内存,物理内存是实打实的,我只有这么多,用的时候你只能用这么多. 为了解决一些问题,产生虚拟内存,通过虚拟内存可以让我们每个进程都能拥有虚拟的3G ...
- Linux内存管理宏观篇(一):不同角度去看内存(硬件)
1.硬件角度 大家都曾经看过那个纸上打孔,记录数据的图片. 后来都知道出现了内存器,我们执行指令分为加载+运行. 最开始的程序运行时只能跑一个进程的,那就不需要复杂的内存管理,把我弄到固定的位置,然后 ...
- linux内存管理_浅谈Linux内存管理
1. 扫盲篇 1.1 操作系统存储层次 常见的计算机存储层次如下: 寄存器:CPU提供的,读写ns级别,容量字节级别. CPU缓存:CPU和CPU间的缓存,读写10ns级别,容量较大一些,百到千节. ...
- android内存池,两种常见的内存管理方法:堆和内存池
描述 本文导读 在程序运行过程中,可能产生一些数据,例如,串口接收的数据,ADC采集的数据.若需将数据存储在内存中,以便进一步运算.处理,则应为其分配合适的内存空间,数据处理完毕后,再释放相应的内存空 ...
- Spark内存管理(2)—— 统一内存管理
Spark内存管理系列文章: Spark内存管理(1)-- 静态内存管理 堆内内存 Spark 1.6之后引入的统一内存管理机制,与静态内存管理的区别在于Storage和Execution共享同一块 ...
- Spark内存管理(1)—— 静态内存管理
Spark内存管理简介 Spark从1.6开始引入了动态内存管理模式,即执行内存和存储内存之间可以相互抢占 Spark提供了2种内存分配模式: 静态内存管理 统一内存管理 本系列文章将分别对这两种内 ...
最新文章
- 初学者SQL语句介绍
- hdc mfc 画扇形图_使用echarts绘制条形图和扇形图
- 软件测试培训分享:软件测试初期怎么面试工作?
- vue-route动态路由
- html点击旋转180,关于点击三角丝滑旋转180度css3 jq处理方法
- 图像浏览界面缩放和平移操作的实现
- css文件修改后没变化 static_Go Web编程使用Go语言创建静态文件服务器
- apache mediawiki 安装_如何在CentOS 7上安装MediaWiki
- Ubuntu安装LNMP
- python反向代理服务器_主机、服务器,代理服务器,反向代理服务器理解(自用)...
- 第五章 线性回归 学习笔记中
- qt制作2048小游戏
- 李航统计学习方法 Chapter1 统计学习方法概论
- 去掉office标题前的黑点
- 基于SSM学生学籍管理系统
- c语言lst文件,Keil C51 之LST文件
- OpenCV每日函数 几何图像变换模块 (8) remap函数
- 【kali】kali2020.2安装 超级详细教程
- 数据库连接池的管理思想
- 计算机游戏的最新技术,搭载十代酷睿i7处理器 这台ROG冰刃4新锐拥有媲美台式游戏电脑的性能...
热门文章
- Hadoop在master查看live nodes为0解决方案
- 2018_11_25_生活记录
- 739.每日温度 (力扣leetcode) 博主可答疑该问题
- android手机和荣耀哪个版本好,【求测评】荣耀v40轻奢版与荣耀X10哪款更好?图文爆料分析...
- java list加入listview_将卡添加到ListView
- Canu FAQ常见问题
- springmvc 使用Jackson的配置
- Bootstrap-1
- 三种GDB类型的转换后字段类型的变化
- 浅谈Android选项卡(二)