因为延迟执行引起的一次内存泄漏的排查

还是按照常规思路,先查GC日志,定位为缓慢累计的内存泄露问题。然后把dump文件加载出来,最后的结果如下
在这里插入图片描述
两个关于线程池的类的对象和一个关于业务代码的内部类。很快定位到了如下代码

        getScheduledThreadPoolExecutor().scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
				...
            }
        }, 0, 1, TimeUnit.MINUTES);

上面代码在执行中会被多次执行(到OOM实际上已经执行了上千万次)。这里显然发现程序员犯了望文生义的错误。scheduleWithFixedDelay不是说延迟一段时间后执行一次,而是延迟一段时间后执行完毕后,再延迟相同的一段时间,再执行。推荐他使用搭配DelayQueue的线程池。实例代码如下

public class TestMain {
    public static void main(String[] args) {

        DelayQueue queue = new DelayQueue<>();//延迟队列
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,10,1000l, TimeUnit.MILLISECONDS,queue);
        threadPoolExecutor.prestartAllCoreThreads();
        threadPoolExecutor.execute(new TaskInfo(1000,1));
        threadPoolExecutor.execute(new TaskInfo(2000,2));
        threadPoolExecutor.execute(new TaskInfo(3000,3));
        threadPoolExecutor.execute(new TaskInfo(4000,4));

    }
}

class TaskInfo implements Delayed,Runnable {

    //标记任务
    private int id;
    //具体时间
    private long excuteTime;

    public TaskInfo(long delay,int id) {
        this.excuteTime = TimeUnit.NANOSECONDS.convert(delay, TimeUnit.MILLISECONDS)+System.nanoTime();
        this.id = id;
    }

    @Override
    public long getDelay(@NotNull TimeUnit unit) {
        return unit.convert(this.excuteTime- System.nanoTime() , TimeUnit.NANOSECONDS);
    }

    @Override
    public int compareTo(@NotNull Delayed o) {
        TaskInfo msg = (TaskInfo)o;
        return this.excuteTime>msg.excuteTime?1:( this.excuteTime<msg.excuteTime?-1:0);
    }

    @Override
    public void run() {
        System.out.println("run task:"+id);
    }
}

—————————————————我是求真务实的分界————————————————————————
我们来具体分析产生OOM的原理。

    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (delay <= 0)
            throw new IllegalArgumentException();
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(-delay));
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }

其中ScheduledFutureTask的run方法是在执行后会把这个任务再放回任务队列。这样保证任务能够反复进行。

private class ScheduledFutureTask<V>
            extends FutureTask<V> implements RunnableScheduledFuture<V> {


        ScheduledFutureTask(Runnable r, V result, long ns, long period) {
            super(r, result);//内部会调用Executors.callable(runnable, result);把Runnable对象转化为Callable对象(RunnableAdapter)。这里解释了为什么RunnableAdapter对象会有很多。
            this.time = ns;
            this.period = period;
            this.sequenceNumber = sequencer.getAndIncrement();
        }

        public void run() {
            boolean periodic = isPeriodic();
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
            else if (!periodic)
                ScheduledFutureTask.super.run();
            else if (ScheduledFutureTask.super.runAndReset()) {
                setNextRunTime();
                reExecutePeriodic(outerTask);
            }
        }
 }       

所谓的RunnableAdapter,顾名思义,就是把一个Runnable对象通过适配器模式转化为一个Callable的对象。

    public static Callable<Object> callable(Runnable task) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<Object>(task, null);
    }

delayedExecute会把这个task追加到自己的workQueue中。

    /**
     * Main execution method for delayed or periodic tasks.  If pool
     * is shut down, rejects the task. Otherwise adds task to queue
     * and starts a thread, if necessary, to run it.  (We cannot
     * prestart the thread to run the task because the task (probably)
     * shouldn't be run yet.)  If the pool is shut down while the task
     * is being added, cancel and remove it if required by state and
     * run-after-shutdown parameters.
     *
     * @param task the task
     */
    private void delayedExecute(RunnableScheduledFuture<?> task) {
        if (isShutdown())
            reject(task);
        else {
            super.getQueue().add(task);
            if (isShutdown() &&
                !canRunInCurrentRunState(task.isPeriodic()) &&
                remove(task))
                task.cancel(false);
            else
                ensurePrestart();
        }
    }

初始化ScheduledThreadPoolExecutor时,可以看到没有选择,使用的内部类DelayedWorkQueue。该队列的视线实际上就是DelayQueue,但是区别在于没有设置容量设置!!

Using a custom queue (DelayedWorkQueue), a variant of unbounded DelayQueue. 
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

这个该死的没有容量限制,最终导致了本次OOM的发生。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值