当前位置: 首页 > 后端技术 > Java

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

时间:2023-04-01 16:57:45 Java

1.前言最近在为客户调优的过程中,经常遇到java调用groovy的情况,在排查过程中发现了一些相关的性能瓶颈。其中比较突出的是调用groovyapi导致的频繁类加载问题。针对这个问题,在本地模拟了客户端相关的代码实现来分析解决。2.症状(1)CPU消耗高1.加载groovyjar时进行路径检查2.读取jar包时进行解压操作(2)类加载耗时响应时间很长,至少在秒级,所以我赢了'在此处发布相关屏幕截图。3.问题排查以下内容以SimpleTemplateEngine获取占位符的值为例。以下是本地模拟情况,重点介绍故障排除思路。(1)第一个优化1.Threaddump,在dump文件中,线程栈中的方法调用一目了然在某个线程中,我们找到类加载相关的方法,直接看最近的自定义类。开始时不要陷入各种框架或组件方法的泥潭。看到这里是TestSimpleTemplateEngine类的generateMessage方法。直接反编译类,查看方法如下:每次调用该方法都会新建SimpleTemplateEngine类(调优过程中客户端的代码是这样实现的),每次客户端的请求都会调用该方法.2.尝试单例方案?看到这里,第一个优化思路是用单例实现newSimpleTemplateEngine()操作。结果当然是:什么都没有。没办法,只能从源码入手。(二)开始源码,第二次优化1.SimpleTemplateEngine类?阅读了SimpleTemplateEngine类的源码,发现它在实例化的时候根本没有搞什么花样,只是设置了一个成员属性groovyShell。真正涉及类加载器的是SimpleTemplateEngine的createTemplate方法。2.createTemplate方法createTemplate的源码如下:关键点就在这个方法中,通过传入的文本模板解析groovy脚本等一系列操作,会将文本脚本封装成一个GroovyCodeSource类,注意它会自动在此处为您的脚本生成一个以groovy后缀结尾的文件名。filename中的计数器是自增的,所以每次调用createTemplate生成的filename都不一样。稍后将给定的groovy代码转换为java类时,将执行类加载。在加载类之前,它会检查类class是否已经存在于缓存(HashMap)中,如果存在则不会调用类加载器进行类加载。直接跳转到解析类的地方:具体加载过程如上源码。缓存检查是基于上面提到的GroovyCodeSource的name属性。那么问题来了,这个名字每次生成都会变,也就是说这里加载类时永远不会使用缓存,每次都需要调用类加载器进行类加载。3.开始真正的优化做好缓存。由于真正导致类加载的是createTemplate方法,所以直接缓存了createTemplate生成的Template实例。缓存有什么用?最简单直接的就是Map。当然,如果涉及的模板类型较多,使用Map可能会占用大量内存。有没有Guava、Caffeine等高性能本地缓存框架?玩LRU的时候,比每次类加载都会过期。.当然,如果每个模板类型不同,不管有没有缓存,效果都是一样的。我还没有想到针对这种情况的解决方案。4.优化后的结果所有线程栈都没有类加载的影子(因为打印了返回值,日志量比较大,都是logging);而如果你关注一下优化前后的GC,你会发现优化后的GC情况也好不到一丁点。同样是-Xmx1g的情况,调优前会频繁FullGC,优化后只有MinorGC。五、测试代码优化前publicclassTestSimpleTemplateEngine{privatefinalstaticLoggerlog=LoggerFactory.getLogger(TestSimpleTemplateEngine.class);privatefinalstaticArrayListstrTemplates=newArrayList();static{strTemplates.add("${user.name}");strTemplates.add("${user.code}");strTemplates.add("${user.company}");strTemplates.add("${user.address}");strTemplates.add("${user.message}");}publicstaticStringgenerateMessage(Mapmap,StringplaceHolder){Stringmsg=null;尝试{msg=newSimpleTemplateEngine().createTemplate(placeHolder).make(map).toString();}catch(IOExceptione){e.printStackTrace();}catch(ClassNotFoundExceptione){e.printStackTrace();}返回消息;}publicstaticvoidmain(String[]args)throwsIOException,ClassNotFoundException{for(inti=0;i<10;i++){newThread(()->{for(;;){Mapmap=newHashMap<>();intnameSuffix=newRandom().nextInt(900)+100;intindex=newRandom().nextInt(strTemplates.size());//这里整个POJO没问题,只要相关属性对了PersonuserDo=newPerson();userDo.setName("TestGroovy"+nameSuffix);userDo.setCode(666);//添加域对象map.put("user",userDo);StringplaceHolder=(String)strTemplates.get(index);StringuserName=generateMessage(map,placeHolder);log.info(placeHolder+":"+userName+Thread.currentThread().getName());}}).start();}}}优化后publicclassTestSimpleTemplateEngineAfterTuning{privatefinalstaticLoggerlog=LoggerFactory.getLogger(TestSimpleTemplateEngineAfterTuning.class);//添加模型类缓存privatefinalstaticConcurrentHashMaptemplateCaches=newConcurrentHashMap<>();privatefinalstaticArrayListstrTemplates=newArrayList();static{strTemplates.add("${user.name}");strTemplates.add("${user.code}");strTemplates.add("${user.company}");strTemplates.add("${user.address}");strTemplates.add("${user.message}");}publicstaticTemplategetTemplate(StringplaceHolder)抛出IOException,ClassNotFoundException{Templatetemplate=templateCaches.get(placeHolder);如果(模板!=null)返回模板;template=newSimpleTemplateEngine().createTemplate(placeHolder);templateCaches.put(placeHolder,template);返回模板;}publicstaticStringgenerateMessage(Mapmap,StringplaceHolder){Stringmsg=null;尝试{msg=getTemplate(placeHolder).make(地图).toString();}catch(IOExceptione){e.printStackTrace();}catch(ClassNotFoundExceptione){e.printStackTrace();}返回消息;}publicstaticvoidmain(String[]args){for(inti=0;i<10;i++){newThread(()->{for(;;){Mapmap=newHashMap<>();intnameSuffix=newRandom().nextInt(900)+100;intindex=newRandom().nextInt(strTemplates.size());PersonuserDo=newPerson();userDo.setName("TestGroovy"+nameSuffix);userDo.setCode(666);map.put("user",userDo);StringplaceHolder=(String)strTemplates.get(index);StringuserName=generateMessage(map,placeHolder);log.info(占位符+":"+userName+Thread.currentThread().getName());}})。开始();}}}