1

这次调优横跨java和Groovy(SimpleTemplateEngine)

 2 years ago
source link: https://segmentfault.com/a/1190000040821859
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

  最近给客户调优过程中,频繁遇到java调用groovy的情况,在排查过程中也发现了一些相关的性能瓶颈。其中比较突出的是调用groovy api时导致的频繁类加载问题,就这个问题在本地模拟了客户相关的代码实现,进行分析解决。
image.png

二、问题现象

(一)高CPU消耗

1. 加载groovy jar时的路经检查

image.png

2. 读取jar包时的解压缩操作

image.png

(二)类加载耗时

  最直观是压测过程中,相关的单接口响应时间很长,至少是秒级起步,这边就不放相关的截图了。

三、问题排查

  以下内容以调用SimpleTemplateEngine获取占位符的值为例,以下为本地模拟情况,重在排查思路。

(一)第一次优化

1. 线程dump,在dump文件中,线程栈中个方法调用一目了然

  在某个线程中,我们找到了类加载相关的方法,直接看最近的自定义类的方法,一开始千万不要陷入各种框架或组件的方法。这里看到是TestSimpleTemplateEngine类的generateMessage方法。
image.png
直接反编译类,查看方法如下:
image.png
每次调用此方法都会new SimpleTemplateEngine类(调优过程中,客户那边的代码就是这么实现的),而且每个客户端的请求都会调用到这个方法。

2. 尝试单例解决

  看到这里,第一个优化思路就是将new SimpleTemplateEngine()这个操作使用单例实现。
当然结果是:毫无成效。没办法,只能从源码开始了。

(二)源码开始,第二次优化

1. SimpleTemplateEngine类

  阅读SimpleTemplateEngine类源码,发现它在实例化的时候根本没做啥骚操作,就是设置了个成员属性groovyShell。真正涉及到类加载器的是SimpleTemplateEnginecreateTemplate方法。

2. createTemplate方法

  createTemplate源码如下:
image.png
重点都在这个方法里,通过传入的文本模版解析groovy脚本等一系列操作,其中会把文本脚本封装成一个GroovyCodeSource类,注意这边它会自动给你的脚本生成一个groovy后缀名结尾的文件名。
filename中counter是自增,所以每次调用createTemplate生成的filename都是不一样的。后面将给定的groovy code转换成java类的时候,会进行类加载。类加载的之前会检查缓存(HashMap)中是否已存在class类,如果存在则不会调用类加载器进行类加载。
直接跳到开始解析类的地方:
image.png
具体加载过程如上源码。其中缓存的检查是就是根据上面提到的GroovyCodeSourcename属性。那么问题来了,这个name每次生成的时候每次都会变化,也就是说这边类加载的时候永远用不到缓存,每次都需要调用类加载器进行类加载。

3. 开始真正的优化了

  缓存大法好。既然真正导致类加载的是createTemplate方法,就直接把createTemplate生成的Template实例给缓存了。缓存用什么呢?最简单直接的就是Map了。当然如果涉及的模版类型比较多,用Map的话可能会占用大量内存,不怕不是还有Guava,Caffeine这种高性能本地缓存框架吗,LRU耍起来,会过期总比每次类加载来的好吧。当然如果每个模版类型都是不一样的,加不加缓存效果都一样,这种情况暂时还没想到解决方法。
image.png

四、优化后结果

  所有的线程栈中都没有类加载的影子了(因为打印了返回值,日志量比较大,都在刷日志);而且如果关注优化前后的GC情况的话,会发现优化后GC情况好了不是一点点,在同样-Xmx1g的情况下,调优前会频繁的Full GC,优化后只有Minor GC。
image.png

五、测试代码

public class TestSimpleTemplateEngine {

    private final static Logger log = LoggerFactory.getLogger(TestSimpleTemplateEngine.class);
    private final static ArrayList strTemplates = new ArrayList();

    static {
        strTemplates.add("${user.name}");
        strTemplates.add("${user.code}");
        strTemplates.add("${user.company}");
        strTemplates.add("${user.address}");
        strTemplates.add("${user.message}");
    }
    public static String generateMessage(Map map, String placeHolder) {
        String msg = null;
        try {
            msg = new SimpleTemplateEngine().createTemplate(placeHolder).make(map).toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return msg;
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (;;) {
                    Map<String,Object> map = new HashMap<>();
                    int nameSuffix = new Random().nextInt(900) + 100;
                    int index = new Random().nextInt(strTemplates.size());
                    // 这里随便整个POJO都行,只要相关的属性对的上就行
                    Person userDo = new Person();
                    userDo.setName("TestGroovy" + nameSuffix);
                    userDo.setCode(666);
                    // 添加域对象
                    map.put("user",userDo);
                    String placeHolder = (String) strTemplates.get(index);
                    String userName = generateMessage(map, placeHolder);
                    log.info(placeHolder + ": " + userName + Thread.currentThread().getName());
                }
            }).start();
        }

    }

}
public class TestSimpleTemplateEngineAfterTuning {

    private final static Logger log = LoggerFactory.getLogger(TestSimpleTemplateEngineAfterTuning.class);
    // 添加模版类缓存
    private final static ConcurrentHashMap<String, Template> templateCaches = new ConcurrentHashMap<>();
    private final static ArrayList strTemplates = new ArrayList();

    static {
        strTemplates.add("${user.name}");
        strTemplates.add("${user.code}");
        strTemplates.add("${user.company}");
        strTemplates.add("${user.address}");
        strTemplates.add("${user.message}");
    }

    public static Template getTemplate(String placeHolder) throws IOException, ClassNotFoundException {
        Template template = templateCaches.get(placeHolder);
        if (template != null) return template;
        template = new SimpleTemplateEngine().createTemplate(placeHolder);
        templateCaches.put(placeHolder, template);
        return template;
    }

    public static String generateMessage(Map map, String placeHolder) {
        String msg = null;
        try {
            msg = getTemplate(placeHolder).make(map).toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return msg;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (; ; ) {
                    Map<String, Object> map = new HashMap<>();
                    int nameSuffix = new Random().nextInt(900) + 100;
                    int index = new Random().nextInt(strTemplates.size());
                    Person userDo = new Person();
                    userDo.setName("TestGroovy" + nameSuffix);
                    userDo.setCode(666);
                    map.put("user", userDo);
                    String placeHolder = (String) strTemplates.get(index);
                    String userName = generateMessage(map, placeHolder);
                    log.info(placeHolder + ": " + userName + Thread.currentThread().getName());
                }

            }).start();
        }
    }
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK