一、任务精确性

通过前两节的分析,大概知道了Timer的运行原理,下面说说使用Timer需要注意的一些事项。下面是Timer简单原理图

从上图可以看到,真正运行闹钟的是一个单线程。也就是说队列中的闹钟,只能依次进行串行化的操作,闹钟的定时执行得不到保证。

比如下面的例子(本节所有代码只列出关键部分,下同)

public class ScheduleDemo {

public static void main(String[] args) throws Exception {

Timer timer = new Timer();

timer.schedule(new AlarmTask("闹钟"),1000,2000);

}

static class AlarmTask extends TimerTask {

public void run() {

log.info(new Date() +" 嘀。。。");

Thread.sleep(10_000); //模拟闹钟执行时间

}

}

}

从下面的运行结果可以看到,预期2秒以后运行的闹钟,推迟到了10秒以后。

Fri Nov 16 14:49:39 CST 2018 嘀。。。

Fri Nov 16 14:49:49 CST 2018 嘀。。。

下面是闹钟运行的时序图

解决方法

针对上面的情况,用户可在AlarmTask.run()里面再开一个异步线程,让TimerThread及时返回,执行队列中后续的闹钟。

public class ScheduleDemo {

public static void main(String[] args) throws Exception {

Timer timer = new Timer();

timer.schedule(new AlarmTask("闹钟"),1000,2000);

}

static class AlarmTask extends TimerTask{

static ExecutorService threadPool = Executors.newCachedThreadPool();

public void run() {

// 建立线程池,提高线程的复用,避免线程创建与上下文切换所带来的开销

threadPool.execute(new Runnable() {

public void run() {

log.info(new Date()+" 嘀。。。");

Thread.sleep(10_000); //模拟闹钟执行时间

}

});

}

}

}

从下面的运行结果可以看到,所有的闹钟执行间隔符合预期的2秒。

Fri Nov 16 15:37:59 CST 2018 嘀。。。

Fri Nov 16 15:38:01 CST 2018 嘀。。。

Fri Nov 16 15:38:03 CST 2018 嘀。。。

Fri Nov 16 15:38:05 CST 2018 嘀。。。

Fri Nov 16 15:38:07 CST 2018 嘀。。。

Fri Nov 16 15:38:09 CST 2018 嘀。。。

下面是异步执行的时序图

通过异步执行任务的方式虽然保证了执行时间的准确性,但也会出现以下问题:

1. 操作系统一般对线程总量加以限制,比如linux下的/proc/sys/kernel/threads-max。当系统并发量很高的时候,开异步会影响其他应用的线程使用。

2. 如果当前系统运行着计算密度型应用,在CPU使用率很高的情况下将会出现排队现象。

3. JVM会给每一个线程分配栈内存,如果Timer分配的任务过多,将很快出现内存溢出的情况。

二、内存泄漏

第二个需要注意的问题是,当用户取消了一个任务以后,失效的任务依然会占据着queue队列,造成内存泄漏,下面是取消任务的源码。

public abstract class TimerTask implements Runnable {

final Object lock = new Object();

int state = VIRGIN;

static final int CANCELLED = 3;

public boolean cancel() {

synchronized(lock) {

boolean result = (state == SCHEDULED);

state = CANCELLED;

return result;

}

}

可以看到TimerTask.cancel()仅仅只是修改task的状态值,并没有及时清理失效的任务。纵观整个Timer源码,唯一进行自我清理是在TimerThread中维护的(前提是当前失效的任务优先级最高)。

class TimerThread extends Thread {

private TaskQueue queue;

public void run() {

mainLoop();

}

private void mainLoop() {

while (true) {

synchronized(queue) {

task = queue.getMin();

synchronized(task.lock) {

if (task.state == TimerTask.CANCELLED) {

// 整个Timer中唯一维护自我清理的地方

queue.removeMin();

continue;

}

}

}

}

}

}

下面列举一个内存泄漏的例子。

public class ScheduleDemo {

public static void main(String[] args) throws Exception {

Timer timer = new Timer();

int i = 0;

timer.schedule(new AlarmTask("闹钟"+i++),100,100);

while(true){

TimerTask alarm = new AlarmTask("闹钟"+i);

timer.schedule(alarm,100,10_0000);

alarm.cancel();

Thread.yield();

log.info("已取消闹钟"+i++);

}

}

static class AlarmTask extends TimerTask{

String name ;

byte[] bytes = new byte[10*1024*1024]; //模拟业务数据

public AlarmTask(String name){

this.name=name;

}

@Override

public void run() {

log.info("["+name+"]嘀。。。");

}

}

}

为了快速暴露问题,特意增加了闹钟实例的大小;同时限制了jvm的堆内存分配

-Xmx100M -Xms100M

运行结果如下

已取消闹钟1

已取消闹钟2

已取消闹钟3

已取消闹钟4

已取消闹钟5

已取消闹钟6

已取消闹钟7

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

at com.haanoo.schedule.ScheduleDemo$AlarmTask.(ScheduleDemo.java:25)

at com.haanoo.schedule.ScheduleDemo.main(ScheduleDemo.java:15)

[闹钟0]嘀。。。

[闹钟0]嘀。。。

从运行的结果看出,失效闹钟没有被及时清理,且很快造成了OOM(主线程因OOM异常退出,而TimerThread线程不受影响)。

有人会想:会不会GC没有运行,或来不及运行而导致OOM?下面看一下GC日志,同时dump一下OOM时的堆内存,方便后面MAT分析

-XX:+PrintGC -XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=d:/timer.dump

下面是运行结果

已取消闹钟1

[GC (Allocation Failure) 24103K->21319K(98304K), 0.0187832 secs]

已取消闹钟2

已取消闹钟3

[GC (Allocation Failure) 42289K->41792K(98304K), 0.0081251 secs]

已取消闹钟4

已取消闹钟5

[GC (Allocation Failure) 63024K->62160K(98304K), 0.0079021 secs]

[Full GC (Ergonomics) 62160K->62038K(98304K), 0.0261820 secs]

已取消闹钟6

已取消闹钟7

[Full GC (Ergonomics) 83014K->82518K(98304K), 0.0083257 secs]

[Full GC (Allocation Failure) 82518K->82503K(98304K), 0.0088677 secs]

java.lang.OutOfMemoryError: Java heap space

Dumping heap to d:/timer.dump ...

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

at com.haanoo.schedule.ScheduleDemo$AlarmTask.(ScheduleDemo.java:25)

at com.haanoo.schedule.ScheduleDemo.main(ScheduleDemo.java:15)

[闹钟0]嘀。。。

Heap dump file created [85271860 bytes in 0.052 secs]

从日志可以看出GC一直在努力,中间进行了3次Full GC(此时会影响应用性能),但基本没啥效果。

再用MAT看一下堆快照

通过MAT观察则一目了然,失效的7个闹钟(每个10M)占据了70M堆内存。

通过上面的分析可以看到,虽然TimeTask.cancel()提供了一个及时取消的接口,但却没有一个自动机制保证失效的任务及时回收(需要用户手动处理)。

解决方法

为了防止内存泄漏,Timer提供了一个接口purge()及时清除无效任务。

public class Timer {

private final TaskQueue queue = new TaskQueue();

public int purge() {

int result = 0;

synchronized(queue) {

for (int i = queue.size(); i > 0; i--) {

if (queue.get(i).state == TimerTask.CANCELLED) {

// 清除无效任务

queue.quickRemove(i);

result++;

}

}

if (result != 0)

// 重新整理队列中得任务

queue.heapify();

}

return result;

}

用户只要合理地使用timer.purge()就能避免内存泄漏,遗憾地是在我所接触的项目中,(或许没有引起重视)基本没有用到这个接口方法。

java 精确定时_Java定时任务Timer调度器【三】 注意事项(任务精确性与内存泄漏)...相关推荐

  1. java timertask 定时_Java定时任务--Timer和TimerTask

    1.程序和编程 我们知道,计算机程序是一组计算机能识别和执行的指令,运行于电子计算机上,满足人们某种需求的信息化工具. 简单来说程序在本质上是指令的集合,而编程从字面上来讲就是编写这些指令集合(程序) ...

  2. java实现 定时_Java实现定时任务的几种方案重构

    聊聊定时任务 定时任务,顾名思义就是,定时去完成某项任务,例如指定某个时间点去做某件事情或者是指定一定的时间间隔去做某些事情.使用定时任务就是一种对生产力的解放,如果没有定时任务的话,对于以上的需求, ...

  3. java 设计模式 示例_Java中的中介器设计模式-示例教程

    java 设计模式 示例 中介者模式是行为设计模式之一 ,因此它处理对象的行为. 中介器设计模式用于在系统中不同对象之间提供集中式通信介质. 根据GoF,中介者模式意图是: 通过封装不同对象集相互交互 ...

  4. java 邮件 定时_Java定时发送邮件

    背景 甲方爸爸:新接入业务在国庆以及军运会期间需要每天巡检业务并发送邮件告知具体情况! 我司:没问题. 甲方爸爸:假期也要发噢. 我司:没问题(...). 刚开始计划指定几个同事轮流发送,业务只要不被 ...

  5. java spring包_java 自定义加载器,加载spring包,动态加载实现,jar包隔离,tomcat加载webapp方式...

    java 自定义加载器,加载spring包,动态加载实现,jar包隔离,tomcat加载webapp方式 发布时间:2018-08-20 12:02, 浏览次数:774 , 标签: java spri ...

  6. java中垃圾收集_Java中的垃圾收集器是什么?

    如果无法从任何活动线程或任何静态引用访问对象,则该对象将有资格进行垃圾收集或GC. 换句话说,如果一个对象的所有引用都是空的,那么它就有资格进行垃圾回收.循环依赖关系不算为引用,因此如果对象A引用对象 ...

  7. java wait定时_java定时器的使用(Timer)

    java定时器的使用(Timer) 1.在应用开发中,常常须要一些周期性的操作,比方每5分钟运行某一操作等. 对于这种操作最方便.高效的实现方式就是使用java.util.Timer工具类. priv ...

  8. java一定时间间隔的定时任务_Java 定时任务---Timer

    本文来自我一个朋友的个人博客(希望各位以后多多支持):https://www.liupeng.mobi/archives/777 一.Timer类 在java中一个完整的定时任务需要由Timer和Ti ...

  9. java 邮件 定时_java实现邮件定时发送

    需求:系统定时发送E-mail到其客户,达到通知的效果.先将实例分享给大家,如果确实有一些帮助的话,请大家来点掌声! 首先介绍java定时器(java.util.Timer)有定时执行计划任务的功能, ...

最新文章

  1. 测试服务命名和动态注册路由的方式@Xan
  2. 揭秘一个操作灰色关键词牟取暴利的案例
  3. Redis 16 大应用场景,竟然这么多。。
  4. 《Linux命令行与shell脚本编程大全 第3版》Linux命令行---10
  5. 用flash做古诗动画_带孩子用两张A4纸做动画(内附资源可下载)
  6. PyCharm平台下初学Django框架
  7. ObjectDetecionAPI TypeError: __new__() got an unexpected keyword argument 'serialized_options'
  8. Chapter 5 Blood Type——15
  9. 《Redis官方教程》-Redis安全
  10. springboot简单的整合swagger
  11. websphere一直安装部署_构建独立部署系统,从部署规范开始
  12. 2021年全球及中国企业级路由器市场竞争格局分析,呈现出寡头竞争的市场格局「图」
  13. 手把手带撸Junior AlphaGo算法「AI工程论」
  14. 苏州大学计算机考研大纲2021,2021苏州大学考研参考书目
  15. 修改egret引擎源码
  16. 点到点法式平面投影点的计算
  17. 39岁了,我依然要谈梦想
  18. 在线APP设计平台,APP在线开发工具有哪些?
  19. 【oracle】oracle创建表、创建索引、创建自增id
  20. 关于sizeof()使用的几个圈套

热门文章

  1. 仿微信查看系统图片缩略图选择多张图片
  2. 记录linux使用yum安装时提示网络不可达和Could not retrieve mirrorlist
  3. Java中final修饰符的理解
  4. 数字化新基建时期如何打造智慧电网的未来
  5. html加载图片有超时时间吗,[前端]图片预加载方法
  6. 没长一张萝莉的脸不要紧,可是你敢不敢有颗爷们的心
  7. Wapiti_wapiti_wapiti使用教程_wapiti是什么意思
  8. docker国内镜像源
  9. Android 11.0 recovery页面旋转180度问题的解决方案
  10. python基础编程:selenium+python实现自动登陆QQ邮箱并发送邮件功能