赞
踩
本文将探讨ThreadLocal和ThreadPoolExecutor中可能存在的内存泄露问题,并提出相应的防范策略。
ThreadPoolExecutor是一个线程池类,它可以管理和复用线程,从而提高程序的性能和稳定性。但是,如果使用不当,ThreadPoolExecutor也会导致内存泄露问题。
首先来说,如果我们在使用ThreadPoolExecutor时没有正确地关闭线程池,就会导致线程一直存在,从而占用大量的内存。为了避免这种情况的发生,我们可以在程序结束时手动关闭线程池。具体来说,我们可以在finally块中调用shutdown方法,从而确保线程池一定会被关闭。
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue);
try {
// do something
} finally {
executor.shutdown();
}
不过在实际生产过程中,大多数时候并不能关闭线程池,为了在无法关闭线程池的运行生产环境中防止内存泄漏
ThreadLocal是一个多线程编程中常用的工具类,它允许我们在每个线程中存储和获取对象,而不必担心线程安全问题。但是,如果使用不当,ThreadLocal也会导致内存泄露问题。
通常情况下,我们会在使用完ThreadLocal后将其置为null,以便垃圾回收器可以回收它所占用的内存。但是,如果我们在某些情况下没有将其置为null,那么它就会一直占用内存,直到程序结束。
为了避免这种情况的发生,我们可以使用ThreadLocal的remove方法手动删除已经不再需要的变量。具体来说,我们可以在finally块中调用remove方法,从而确保变量一定会被删除。
ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
try {
threadLocal.set(new Object());
// do something
} finally {
threadLocal.remove();
}
实际项目中在线程池中使用ThreadLocal导致内存溢出的案例,背景是在线程池中发送数据到kafka,并自定义了拒绝策略,在拒绝策略中把拒绝的相关信息打印出来。模拟相关业务代码
public class ThreadPoolUtil { private static ThreadLocal<ThreadLocalMemoryEntity>threadLocal= 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<ThreadLocalMemoryEntity> 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<ThreadLocalMemoryEntity> 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 < 10000; i++) { ThreadLocalMemoryEntity threadLocalMemoryEntity = new ThreadLocalMemoryEntity(); threadLocalMemoryEntity.setName("test" + i); //模拟发送请求 实际生产中每一个请求都会有一个线程 SimulateReqPool.execute(() -> { 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<ThreadLocalMemoryEntity> 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 < 10000; i++) { ThreadLocalMemoryEntity threadLocalMemoryEntity = new ThreadLocalMemoryEntity(); threadLocalMemoryEntity.setName("test" + i); //模拟发送请求 实际生产中每一个请求都会有一个线程 SimulateReqPool.execute(() -> { 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 © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。