一、前言

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

二、问题景象

(一)高CPU耗费

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

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

(二)类加载耗时

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

三、问题排查

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

(一)第一次优化

1. 线程dump,在dump文件中,线程栈中个办法调用高深莫测

  在某个线程中,咱们找到了类加载相干的办法,间接看最近的自定义类的办法,一开始千万不要陷入各种框架或组件的办法。这里看到是TestSimpleTemplateEngine类的generateMessage办法。

间接反编译类,查看办法如下:

每次调用此办法都会new SimpleTemplateEngine类(调优过程中,客户那边的代码就是这么实现的),而且每个客户端的申请都会调用到这个办法。

2. 尝试单例解决

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

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

1. SimpleTemplateEngine类

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

2. createTemplate办法

  createTemplate源码如下:

重点都在这个办法里,通过传入的文本模版解析groovy脚本等一系列操作,其中会把文本脚本封装成一个GroovyCodeSource类,留神这边它会主动给你的脚本生成一个groovy后缀名结尾的文件名。
filename中counter是自增,所以每次调用createTemplate生成的filename都是不一样的。前面将给定的groovy code转换成java类的时候,会进行类加载。类加载的之前会查看缓存(HashMap)中是否已存在class类,如果存在则不会调用类加载器进行类加载。
间接跳到开始解析类的中央:

具体加载过程如上源码。其中缓存的查看是就是依据下面提到的GroovyCodeSourcename属性。那么问题来了,这个name每次生成的时候每次都会变动,也就是说这边类加载的时候永远用不到缓存,每次都须要调用类加载器进行类加载。

3. 开始真正的优化了

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

四、优化后后果

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

五、测试代码

  • 优化前
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();        }    }}