dromara / dynamic-tp

🔥🔥🔥轻量级动态线程池,内置监控告警功能,集成三方中间件线程池管理,基于主流配置中心(已支持Nacos、Apollo,Zookeeper、Consul、Etcd,可通过SPI自定义实现)。Lightweight dynamic threadpool, with monitoring and alarming functions, base on popular config centers (already support Nacos、Apollo、Zookeeper、Consul, can be customized through SPI).
https://dynamictp.cn
Apache License 2.0
3.77k stars 763 forks source link

内存泄漏:默认配置下 DtpExecutor 在 AbortPolicy 拒绝策略下,拒绝时产生内存泄露 #402

Closed SimpleIto closed 7 months ago

SimpleIto commented 7 months ago

版本:

问题呈现

业务在产生200多W次reject后,ThreadPoolStatProvider#stopWatchMap 已达到5164M,且无法被GC回收掉,最终干挂掉。

ThreadPoolStatProvider
    /**
     * stopWatchMap  key -> Runnable  value -> millis
     */
    private final Map<Runnable, Long> stopWatchMap = new ConcurrentHashMap<>();

image

原因分析

本质是在“以抛异常方式”(例如juc的ThreadPoolExecutor#AbortPolicy)拒绝时,其增强代理RejectedInvocationHandler中的afterReject 则会被跳过,从而导致 ThreadPoolStatProvider#stopWatchMap 堆积只增不减。

// 此处默认true 放入stopWatch
PerformanceMonitorAware
    @Override
    public void execute(Executor executor, Runnable r) {
        if (TRUE_STR.equals(System.getProperty(DTP_EXECUTE_ENHANCED, TRUE_STR))) {
            Optional.ofNullable(statProviders.get(executor)).ifPresent(p -> p.startTask(r)); // startTask() -> stopWatchMap.put(r, System.currentTimeMillis());
        }
    }

// 一系列Aware在实际execute前被调用
DtpExecutor
    @Override
    public void execute(Runnable command) {
        command = getEnhancedTask(command);
        AwareManager.execute(this, command); // here
        super.execute(command);
    }

// 关键!!
RejectedInvocationHandler // 会在DtpExecutor初始化时,以动态代理的方式增强我们配置的AbortPolicy
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            beforeReject((Runnable) args[0], (Executor) args[1]);
            Object result = method.invoke(target, args); // 此处抛异常,导致后续afterReject没被执行
            afterReject((Runnable) args[0], (Executor) args[1]); // afterReject -> stopWatchMap.remove(r)
            return result;
        } catch (InvocationTargetException ex) {
            throw ex.getCause();
        }
    }

按上述配置可轻易复现

解决

看了下调用链路,目前可通过配置-Ddtp.execute.enhanced=false 来临时简单绕过put。但考虑到相关特性对stopWatch的使用,还是不太妥。 考虑修改RejectedInvocationHandler,将afterReject合理添至finally

乐意提供PR :)

KamToHung commented 7 months ago

版本:

  • DynamicTp版本:1.1.6.1

问题呈现

业务在产生200多W次reject后,ThreadPoolStatProvider#stopWatchMap 已达到5164M,且无法被GC回收掉,最终干挂掉。

ThreadPoolStatProvider
    /**
     * stopWatchMap  key -> Runnable  value -> millis
     */
    private final Map<Runnable, Long> stopWatchMap = new ConcurrentHashMap<>();

image

原因分析

本质是在“以抛异常方式”(例如juc的ThreadPoolExecutor#AbortPolicy)拒绝时,其增强代理RejectedInvocationHandler中的afterReject 则会被跳过,从而导致 ThreadPoolStatProvider#stopWatchMap 堆积只增不减。

// 此处默认true 放入stopWatch
PerformanceMonitorAware
    @Override
    public void execute(Executor executor, Runnable r) {
        if (TRUE_STR.equals(System.getProperty(DTP_EXECUTE_ENHANCED, TRUE_STR))) {
            Optional.ofNullable(statProviders.get(executor)).ifPresent(p -> p.startTask(r)); // startTask() -> stopWatchMap.put(r, System.currentTimeMillis());
        }
    }

// 一系列Aware在实际execute前被调用
DtpExecutor
    @Override
    public void execute(Runnable command) {
        command = getEnhancedTask(command);
        AwareManager.execute(this, command); // here
        super.execute(command);
    }

// 关键!!
RejectedInvocationHandler // 会在DtpExecutor初始化时,以动态代理的方式增强我们配置的AbortPolicy
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            beforeReject((Runnable) args[0], (Executor) args[1]);
            Object result = method.invoke(target, args); // 此处抛异常,导致后续afterReject没被执行
            afterReject((Runnable) args[0], (Executor) args[1]); // afterReject -> stopWatchMap.remove(r)
            return result;
        } catch (InvocationTargetException ex) {
            throw ex.getCause();
        }
    }

按上述配置可轻易复现

解决

看了下调用链路,目前可通过配置-Ddtp.execute.enhanced=false 来临时简单绕过put。但考虑到相关特性对stopWatch的使用,还是不太妥。 考虑修改RejectedInvocationHandler,将afterReject合理添至finally

乐意提供PR :)

感谢,pr指派给你