用MAT定位高负载线程

背景

某广告引擎服务的负载很高,之前用top、jstack来抓高负载的线程,发现高负载的线程都是没有自定义名称的,用的还是默认的命名方式,如下:

48385 www-data  20   0   17.0t 216.8g 209.6g S  1.6  172 111:21.51 pool-14-thread-
48389 www-data  20   0   17.0t 216.8g 209.6g S  1.6  172 111:21.88 pool-14-thread-
48391 www-data  20   0   17.0t 216.8g 209.6g S  1.6  172 111:26.01 pool-14-thread-
48393 www-data  20   0   17.0t 216.8g 209.6g S  1.6  172 111:31.96 pool-14-thread-
48405 www-data  20   0   17.0t 216.8g 209.6g S  1.6  172 111:26.11 pool-14-thread-
47510 www-data  20   0   17.0t 216.8g 209.6g S  1.3  172  26:31.58 feature-task-8-
48355 www-data  20   0   17.0t 216.8g 209.6g S  1.3  172 111:02.62 pool-14-thread-
48357 www-data  20   0   17.0t 216.8g 209.6g S  1.3  172 111:01.62 pool-14-thread-
48359 www-data  20   0   17.0t 216.8g 209.6g S  1.3  172 111:04.75 pool-14-thread-
48360 www-data  20   0   17.0t 216.8g 209.6g S  1.3  172 111:08.55 pool-14-thread-
48361 www-data  20   0   17.0t 216.8g 209.6g S  1.3  172 111:06.72 pool-14-thread-
48362 www-data  20   0   17.0t 216.8g 209.6g S  1.3  172 111:02.16 pool-14-thread-
48363 www-data  20   0   17.0t 216.8g 209.6g S  1.3  172 111:07.89 pool-14-thread-
48368 www-data  20   0   17.0t 216.8g 209.6g S  1.3  172 111:09.15 pool-14-thread-
48371 www-data  20   0   17.0t 216.8g 209.6g S  1.3  172 110:59.45 pool-14-thread-
48373 www-data  20   0   17.0t 216.8g 209.6g S  1.3  172 111:23.91 pool-14-thread-
48374 www-data  20   0   17.0t 216.8g 209.6g S  1.3  172 111:19.99 pool-14-thread-
48375 www-data  20   0   17.0t 216.8g 209.6g S  1.3  172 111:16.03 pool-14-thread-

我们目前知道的是,高负载线程叫 pool-14-thread-1这种。那只能用MAT分析JVM的dump文件,MAT有分析对象间的引用关系的功能。

准备工作

因为生产的机器都是70G内存,会导致dump文件太大,mac没有那么大的内存来分析几十G的内存文件。

所以找了预发环境的机器来dump,命令如下:

jmap -dump:format=b,file=heap.bin [pid]

如果预发环境的机器内存还是太大,只能改-Xms -Xmx了,改成4G左右。

dump好以后gzip做压缩,便于文件传输,从500M压缩到90M左右。

安装lrzsz,通过sz -be 将压缩文件从预发环境下载到本地。(大文件传输很困难,失败了n次)

下载eclipse,下载MAT插件。

分析

在MAT的线程视图里找到了pool-14-thread-1,如下图:

点击线程,Incomming References可以查到引用了该对象的对象。如下图:

通过两次的查询,可以定位到是aa.bb.ccc.hystrix.FallbackExecutor这个对象。

看下该类的源码:

public class FallbackExecutor {
    private static final Logger LOG = LoggerFactory.getLogger(FallbackExecutor.class);
    // 这里定义线程池,且没有自定义线程名字!!!
    private static final ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);

    public FallbackExecutor() {
    }

    public static void execute() {
        RecoveryStrategy recoveryStrategy = null;

        try {
            String strategy = ConfigProperties.getProperties().getProperty("recovery.strategy", "aa.bb.xxx.hystrix.DefaultRecoveryStrategy");
            recoveryStrategy = (RecoveryStrategy)Class.forName(strategy).newInstance();
            LOG.info("instance recoveryStrategy class:{}", recoveryStrategy);
        } catch (Exception var4) {
            var4.printStackTrace();
            LOG.error("recovery.strategy not exist");
        }

        int interval = Integer.parseInt(ConfigProperties.getProperties().getProperty("recovery.stategy.interval.minutes", "5"));
        executor.scheduleWithFixedDelay(recoveryStrategy, (long)interval, (long)interval, TimeUnit.MINUTES);

        try {
            KafkaSDKFace.storeStrategy = (StoreStrategy)Class.forName(ConfigProperties.getProperties().getProperty("store.strategy", "aa.bb.xxx.hystrix.DefaultStoreStrategy")).newInstance();
            LOG.info("instance storeStrategy class:{}", KafkaSDKFace.storeStrategy);
        } catch (Exception var3) {
            var3.printStackTrace();
            LOG.error("store.strategy not exist");
        }

        executor.scheduleWithFixedDelay(KafkaSDKFace.storeStrategy, (long)interval, (long)interval, TimeUnit.MINUTES);
    }
}

该类主要用于fallback降级。

在repo里查找使用了这个包的地方, xx.yy.zzz.rank.processor.helper.KafkaSdkHelper这个类,代码如下:

    public void writeKafka(Object object, String topic, String type) {
        try {
            if (object != null) {

                KafkaSDKFace sdkFace = getKafkaSDKFace(type);
                // KafkaSDKFace是那个Jar包里的类!!!
                if(sdkFace != null){
                    Message message = new Message(System.currentTimeMillis(), MapperUtil.MAPPER.writeValueAsString(object));
                    sdkFace.send(topic, null, message);
                }
                else {
                    logger.error("sdkFace is null");
                }
            }

        } catch (Exception e) {

            logger.error("kafka error : " + e.getMessage());
            logger.error("kafka stack : " + ExceptionUtils.getStackTrace(e));
        }
    }

到此已经找到了高负载线程的地方

总结

在服务中自定义线程池的时候,一定要进行重命名,不要使用默认的线程名生成方式,否则对于后期的问题定位是个极大的干扰。

原文链接

https://segmentfault.com/a/11...

你可能感兴趣的:(线程池,java,性能分析,eclipse插件)