探究ThreadLocal和ThreadPoolExecutor中的内存泄露风险与防范策略

您所在的位置:网站首页 threadlocal防止内存泄露 探究ThreadLocal和ThreadPoolExecutor中的内存泄露风险与防范策略

探究ThreadLocal和ThreadPoolExecutor中的内存泄露风险与防范策略

2024-07-14 20:55| 来源: 网络整理| 查看: 265

探究ThreadLocal和ThreadPoolExecutor中的内存泄露风险与防范策略

本文将探讨ThreadLocal和ThreadPoolExecutor中可能存在的内存泄露问题,并提出相应的防范策略。

ThreadPoolExecutor的内存泄露问题

ThreadPoolExecutor是一个线程池类,它可以管理和复用线程,从而提高程序的性能和稳定性。但是,如果使用不当,ThreadPoolExecutor也会导致内存泄露问题。

首先来说,如果我们在使用ThreadPoolExecutor时没有正确地关闭线程池,就会导致线程一直存在,从而占用大量的内存。为了避免这种情况的发生,我们可以在程序结束时手动关闭线程池。具体来说,我们可以在finally块中调用shutdown方法,从而确保线程池一定会被关闭。

ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue); try { // do something } finally { executor.shutdown(); }

不过在实际生产过程中,大多数时候并不能关闭线程池,为了在无法关闭线程池的运行生产环境中防止内存泄漏

限制线程池的大小:确保线程池的大小适合工作负载和可用系统资源。过大的线程池可能导致过多的内存消耗,同时尽可能复用线程池,避免在项目中过多的创建线程池使用有界队列:考虑使用有界队列(例如ArrayBlockingQueue)而不是无界队列。有界队列可以控制任务的排队数量,避免无限制的内存增长。优化任务的执行时间:尽量减少任务的执行时间,避免长时间的任务阻塞线程池的线程。定期监控线程池的状态:通过监控线程池的活动线程数、队列大小和任务执行情况,及时发现异常情况并进行调整。 ThreadLocal的内存泄露问题

ThreadLocal是一个多线程编程中常用的工具类,它允许我们在每个线程中存储和获取对象,而不必担心线程安全问题。但是,如果使用不当,ThreadLocal也会导致内存泄露问题。 通常情况下,我们会在使用完ThreadLocal后将其置为null,以便垃圾回收器可以回收它所占用的内存。但是,如果我们在某些情况下没有将其置为null,那么它就会一直占用内存,直到程序结束。 为了避免这种情况的发生,我们可以使用ThreadLocal的remove方法手动删除已经不再需要的变量。具体来说,我们可以在finally块中调用remove方法,从而确保变量一定会被删除。

ThreadLocal threadLocal = new ThreadLocal(); try { threadLocal.set(new Object()); // do something } finally { threadLocal.remove(); }

实际项目中在线程池中使用ThreadLocal导致内存溢出的案例,背景是在线程池中发送数据到kafka,并自定义了拒绝策略,在拒绝策略中把拒绝的相关信息打印出来。模拟相关业务代码

public class ThreadPoolUtil { private static ThreadLocalthreadLocal= new ThreadLocal(); //处理业务的线程池 核心参数设置小保证能进入拒绝策略 private static final ThreadPoolExecutorcompensateBatchPool= new ThreadPoolExecutor( 10, 10, 1, TimeUnit.SECONDS, new ArrayBlockingQueue(1) , new LogPolicy()); //模拟客户端发送请求的线程池 private static final ThreadPoolExecutorsimulateReqPool= new ThreadPoolExecutor( 1000, 1000, 1, TimeUnit.SECONDS, new ArrayBlockingQueue(1000)); public static ThreadPoolExecutor getCompensateBatchPool() { returncompensateBatchPool; } public static ThreadPoolExecutor getSimulateReqPool() { returnsimulateReqPool; } public static ThreadLocal getThreadLocal() { returnthreadLocal; } // 拒绝策略 public static class LogPolicy implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { ThreadLocalMemoryEntity threadLocalMemoryEntity =threadLocal.get(); // 模拟记录内容 System.out.println("执行拒绝策略:"+ threadLocalMemoryEntity.getName()); } } }

业务实体

@Data public class ThreadLocalMemoryEntity { private String name; private byte[] data = new byte[1024*1024]; }

idea配置堆内存空间 Xms512m -Xmx512m

业务代码

public class ThreadLocalMemoryLeakExample { private static ThreadLocal threadLocal = ThreadPoolUtil.getThreadLocal(); private static ThreadPoolExecutor compensateBatchPool = ThreadPoolUtil.getCompensateBatchPool(); private static ThreadPoolExecutor SimulateReqPool = ThreadPoolUtil.getSimulateReqPool(); public static void main(String[] args) { try { for (int i = 0; i { threadLocal.set(threadLocalMemoryEntity); //模拟执行业务逻辑 compensateBatchPool.execute(() -> { //模拟发送kafka消息 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发送kafka数据:" + threadLocalMemoryEntity.getName()); }); }); } } finally { compensateBatchPool.shutdown(); SimulateReqPool.shutdown(); } } }

直接运行mian方法,结果如下 请添加图片描述 虽然GC会不断回收new的ThreadLocalMemoryEntity对象,但是由于不断将ThreadLocalMemoryEntity放入ThreadLocal中,导致内存溢出异常抛出。 如何防范ThreadLocal内存溢出 优化后的业务代码

public class ThreadLocalMemoryLeakExample { private static ThreadLocal threadLocal = ThreadPoolUtil.getThreadLocal(); private static ThreadPoolExecutor compensateBatchPool = ThreadPoolUtil.getCompensateBatchPool(); private static ThreadPoolExecutor SimulateReqPool = ThreadPoolUtil.getSimulateReqPool(); public static void main(String[] args) { try { for (int i = 0; i { try { threadLocal.set(threadLocalMemoryEntity); //模拟执行业务逻辑 compensateBatchPool.execute(() -> { //模拟发送kafka消息 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发送kafka数据:" + threadLocalMemoryEntity.getName()); }); } finally { //防止内存泄露 threadLocal.remove(); } }); } } finally { compensateBatchPool.shutdown(); SimulateReqPool.shutdown(); } } }

注意需要在threadLocal.set 所在的线程 进行 remove才有效,因此在使用ThreadLocal的时候可以遵守这个编程规范

try { threadLocal.set(xxx); }finally { threadLocal.remove(); } 总结

在多线程编程中,ThreadLocal和ThreadPoolExecutor是两个常用的工具类,但是它们也会带来内存泄露的风险。为了避免这种情况的发生,我们可以在使用完毕后手动删除变量或关闭线程池。希望本文能够对您有所帮助。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3