背景,运营反馈接口查询越用越卡。
排查方案,跳板机登录到服务器,top命令查看,内存占用过高,
使用jmap分析:
jmap -histo:live 858524
Thread类型的对象占用的内存最多。
使用jstack命令分析:
jstack -l 858524
可以看到线程池中有很多线程处于WAITING状态,遗憾的是没有对线程池进行命名,不确定是在哪创建的线程池。
在MAT中分析(对本次没实际意义,只单纯使用)
使用jmap -dump:format=b,file=f2 858524命令,将堆信息导出到f2文件,然后在MAT中打开分析:
查看histogram和dominator_tree找出是线程池挂起的问题,去代码中搜索线程池找到问题
实际的代码:
下面是模拟的代码,实际的代码是订单查询过慢,因为调用了其它的微信服务,查用户头像,调用了用户服务查用户昵称,调用了分销系统,查询订单分成,所以当时是用countdownlatch,发起多个线程,并发的去其它服务里查询,但忘记关闭线程池导致,模拟代码如下:
@RestController("testController")
@RequestMapping(UserClient+"/h5/test")
@Slf4j
public class TestController extends BaseController implements AuthCookieAware {
@GetMapping("/test1")
public Response test1() throws Exception{
int n = 300000;
for (int i = 0; i < n; i++) {
test2();
}
return new Response().ok();
}
public Response test2() throws Exception{
ThreadPoolExecutor executor = new ThreadPoolExecutor(10,20,0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(), new NamedThreadFactory("并发线程"));
RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
String jvmName = runtimeBean.getName();
System.out.println("JVM Name = " + jvmName); long pid = Long.valueOf(jvmName.split("@")[0]);
System.out.println("JVM PID = " + pid);
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
for(int j = 0; j<10; j++){
executor.execute(()->{
System.out.println("当前线程总数为:"+bean.getThreadCount());
});
}
//executor.shutdown();
Thread.sleep(1000);
System.out.println("线程总数为 = " + bean.getThreadCount());
return new Response().ok();
}
public class NamedThreadFactory implements ThreadFactory {
private final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup threadGroup;
private final AtomicInteger threadNumber = new AtomicInteger(1);
public final String namePrefix;
NamedThreadFactory(String name){
SecurityManager s = System.getSecurityManager();
threadGroup = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
if (null==name || "".equals(name.trim())){
name = "pool";
}
namePrefix = name +"-"+
poolNumber.getAndIncrement() +
"-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(threadGroup, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
}
原因:线程池在使用完毕后未shutdown(),每次请求过来均会重新新建线程池。
解决方案:线程池使用完毕后要关闭,线程池最好设计成单例模式,线程池最好要命名,方便排查问题。
延伸:单例的线程池:
public class ThreadTool {
public final static int corePoolSize = 2;
public final static int maximumPoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
public final static int keepAliveTime = 60;
public final static TimeUnit unit = TimeUnit.SECONDS;
public final static BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(
200);
private static class Assistant {
public final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public static ThreadPoolExecutor getFixedThreadPool() {
return Assistant.poolExecutor;
}
}
延伸:为什么未关闭线程池会导致内存溢出
ThreadPoolExecutor的通用构造函数如下
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) { ... }
我们首先简单了解以上参数相关的规则
1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
可以看出,allowCoreThreadTimeOut这个方法就像其字面的意思一样,允许Core Thread超时后可以关闭。
要想使线程池没有任务时销毁核心线程,需要启用allowCoreThreadTimeOut(true)同时将core size设置为0,而实际上,core size设置成任意一个正数值就可以,设置成0时,加不加allowCoreThreadTimeOut(true)都没有影响,因为这个方法是对core thread产生影响,但此时core thread为0,而且当新任务进来时,必须等到workQueue满时才会创建新线程,这也不是我们想要的结果,所以日常中不要将核心线程数设置成0,这样的话只有队列满了才会创建线程执行你的任务。
参考文档:https://blog.csdn.net/qq_24082175/article/details/81019490
参考文档:https://www.jianshu.com/p/1d2addd892fa
参考文档:https://blog.csdn.net/weixin_42008012/article/details/104932588