文档章节

Groovy动态加载类踩中的那些坑...

陈孝杰
 陈孝杰
发布于 2017/02/11 10:41
字数 889
阅读 4053
收藏 2

告警显示tp-audit的多个应用间隔性发生GC:ConcurrentMarkSweepCount(OldGC)引起了我们的注意。

现象:

观看我们公司(点评)cat监控平台

这是同一个小时内的图像,由图可知oldgc次数一分钟保持在6次左右,然而老年代内存依然坚挺使用高达900多M,没有下降趋势。

正常应用发生oldgc,老年代内存理论上应该被释放掉绝大部分。

 

由此我们可以判断出程序肯定有某处发生了内存泄漏。

 

用了ha456.jar、MAT、VisualVM、JProfiler等工具和请教各路大神分析得知

AppClassLoader里主要ConcurrentHashMap占用内存最大,而ConcurrentHashMap里主要存放的几乎都是Groovy动态生成的类名

这个ConcurrentHashMap存放了近600万个Entry

 

原因:

AppClassLoader是java内置类加载器,用来加载用户应用程序的类。里面有一个parallelLockMap,

主要用来存储类锁,避免JVM加载同名的类,和提高类加载的并发度。

 

GroovyClassLoader如果加载的是无类名的Script,最终会生成一个随机的类名,每次都不一样。

导致parallelLockMap不断膨胀,幸运的是parallelLockMap只存储一个name和Object,并不会占用过多空间。

所以现象就是上线半个多月都没有问题,一个月后才会内存吃紧。8G的LVS甚至几个月都不会有问题。

 

这应该是一个JDK的BUG,只要加载过多的不同类,parallelLockMap就会不断的膨胀,导致memory leak

最终机器就会宕机。这个这个BUG早已经提给官方,但是JDK7、JDK8都未修复,而且明确指出不修复。

 

结论:

理论上是不合理的,因为这个Map只进不出。既然官方指出不修复,那我们只能规范使用流程了。

无论是Groovy还是通过别的方式动态加载类,尽量使用固定类名。如果类名是随机的,就要控制加载数量了。

如果你是4G的LVS,可以放心的创建500万个不同名的类。毛估500万个不同类名,占用大约800M内存。

如果程序仅加载变更后的类,相信500万次变更是肯定够用的。

如果还不够,那只能通过加内存来解决了。

 

使用Groovy动态加载类就算跳过了自带的无法卸载的类的坑,还是会踩进JDK自带的坑。

此次案例详细bug记录请预览https://issues.apache.org/jira/browse/GROOVY-6655

 

与此次案例无关的BUG预警:

Groovy推荐版本2.3.7,新版本有无法卸载类的坑,很容易导致OOM:PermGen space。

详细bug记录请预览https://issues.apache.org/jira/browse/GROOVY-7913

 

JDK8内存模型更新,默认已经没有PermGen了,也就是说不会发生OOM:PermGen space了。

但是使用的是LVS自带的内存,所以最好还是指定这个参数-XX:MaxMetaspaceSize=128m控制下大小

不然Groovy也会饰无忌惮的吃光LVS内存。

 

Groovy动态加载类使用方式推荐

private static final GroovyClassLoader classLoader = new GroovyClassLoader();

public static Script loadScript(String rule) {

    return loadScript(rule, new Binding());

}

public static Script loadScript(String rule, Binding binding) {

    Script script = null;

    if (StringUtils.isEmpty(rule)) {

        return null;

    }

    try {

        Class ruleClazz = classLoader.parseClass(rule);

        if (ruleClazz != null) {

            log.info("load rule:" + rule + " success!");

            return InvokerHelper.createScript(ruleClazz, binding);

        }

    catch (Exception e) {

        log.error(e.getMessage(), e);

    finally {

        classLoader.clearCache();

    }

    return script;

}

因为

GroovyClassLoader是static的,所以想卸载无引用的Class,要执行classLoader.clearCache();

如果GroovyClassLoader每次都是new出来的,可以忽略执行classLoader.clearCache();

© 著作权归作者所有

陈孝杰
粉丝 4
博文 6
码字总数 5639
作品 0
武汉
程序员
私信 提问
加载中

评论(1)

剑神卓不凡
剑神卓不凡
学习了
Spring对Groovy Bean的支持

Groovy是一个基于JVM的动态语言,对于一个Java开发者,它最让我喜欢的地方就是兼容Java的语法,学习使用成本低。从2.0版本开始,Spring就对Groovy这些动态语言提供了支持,结合Spring和Groov...

joshuazhan
2013/06/16
6.1K
2
8个类,1500行代码搞定插件化

写在前面 本文原创,转载请以链接形式注明地址:http://kymjs.com/code/2016/05/22/01 动态加载一个 Service 到应用中,同样采用的是和 Activity 一样的伪装欺骗系统识别的方案。 接上一篇:...

kymjs张涛
2016/05/22
1K
1
SDK 热修复工具包 - SDKHotfix

介绍 相信 APP 热修复大家都很熟练了,那如果 SDK 想要实现热修复呢? 这就是本项目诞生的背景,让 SDK 开发者能快速赋予 SDK 热修复的能力,不要踩我踩过的坑。 作用 如果你是 SDK 开发者,...

FeelsChaotic
昨天
72
0
Vert.x入坑须知(3)

随着Vert.x进化到3.5.0,本系列也迎来了新篇章。 CORS的新变化 对于CORS,搞Web开发(不论你是前端,还是后端)的同志应该不陌生,尤其是如今微服务盛行的时代,CORS更是最常用的配置项之一。...

胡键
2017/12/03
0
0
基于Groovy实现Spring Bean的动态加载

Spring对Groovy有着良好的支持,能把Groovy实现类作为Bean来使用,在前一篇Blog《Spring对Groovy Bean的支持》有详细的描述http://my.oschina.net/joshuazhan/blog/137940。基于Groovy Bean...

joshuazhan
2013/07/15
9.3K
12

没有更多内容

加载失败,请刷新页面

加载更多

Netty整合Protobuffer

现在我们都知道,rpc的三要素:IO模型,线程模型,然后就是数据交互模型,即我们说的序列化和反序列化,现在我们来看一下压缩比率最大的二进制序列化方式——Protobuffer,而且该方式是可以跨...

算法之名
34分钟前
17
0
如何用C++实现栈

栈的定义 栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压...

BWH_Steven
53分钟前
5
0
编程作业20190210900169

1编写一个程序,提示用户输入名和姓,然后以“名,姓”的格式打印出来。 #include <stdio.h>#include <stdlib.h> int main(){ char firstName[20]; char lastName[20]; print......

1李嘉焘1
今天
12
0
补码的优点及原理分析

只讨论整数 1.计算机内部为什么没有减法器? 减法运算本身其实就是加法,如x - y即x +(-y),所以只需要将负数成功表示出来并可以参加加法运算,那加法器就可同时实现“+”和“-”的运算。这...

清自以敬
今天
76
0
Docker 可视化管理 portainer

官网安装指南: https://portainer.readthedocs.io/en/latest/deployment.html docker-compose.yml 位置,下载地址:https://downloads.portainer.io/docker-compose.yml...

Moks角木
今天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部