记一次使用visualVM分析GroovyClassLoader加载机制导致频繁gc的性能问题

一、现象描述

    通常使用如下代码在Java 中执行 Groovy 脚本:

1 GroovyClassLoader groovyLoader = new GroovyClassLoader();
2 Class<Script> groovyClass = (Class<Script>) groovyLoader.parseClass(groovyScriptFile);
3 Script groovyScript = groovyClass.newInstance();

每次执行groovyLoader.parseClass(groovyScriptFile),Groovy 为了保证每次执行的都是新的脚本内容,会每次生成一个新名字的Class文件对象。但当对同一段脚本每次都执行这个方法时,会导致的现象就是装载的Class会越来越多,从而导致PermGen占用过高,甚至被用满。如下图1所示。

二、现象分析

    visualVM是一款优秀的性能分析工具,可从idea的plugins中下载,具体操作和解释可以看这篇文章 。

    由于是PermGen区的泄漏,通过visualVM分析发现类加载部分有大量的GroovyClassLoader,如下图2所示。这时想到有使用两个服务,内部实现是使用Groovy配置的方式(这里我用Groovy封装了一个服务提供的微框架)。但这两个服务其实使用的并不频繁,所以导致切换后的现象不是雪崩式的集群挂掉,而是逐步导致内存占用升高。

三、现象解决

    定位到问题,事情就好办了,首先看一下处理Groovy加载执行的代码;然后为生成的类加一层对象缓存;由于脚本中用到了Binding上下文对象,为了线程安全性,调整执行时的方式。最后问题得到解决。

1. 原始的调用方式

Object scriptObject = null;
try {
    Binding binding = new Binding();
    binding.setVariable("context", this.context);
    binding.setVariable("clientInfo", clientInfo);
    binding.setVariable("params", params);
    binding.setVariable("data", data);

    GroovyShell shell = new GroovyShell(binding);
    scriptObject = (Object) shell.evaluate(script);
} catch (Throwable t) {
    log.error("groovy script eval error. script: " + script, t);
}

return scriptObject;

 2. 为Groovy Script增加缓存

private Map<String, Object> scriptCache = new ConcurrentHashMap<String, Object>();
...

Object scriptObject = null;
try {
    Binding binding = new Binding();
    binding.setVariable("context", this.context);
    binding.setVariable("clientInfo", clientInfo);
    binding.setVariable("params", params);
    binding.setVariable("data", data);

    Script shell = null;
    if (isCached(cacheKey)) {
        shell = (Script) getCaches().get(cacheKey);
    } else {
        shell = new GroovyShell().parse(script);
    }

    shell.setBinding(binding);
    scriptObject = (Object) shell.run();

    // clear
    binding.getVariables().clear();
    binding = null;

    // Cache
    if (!isCached(cacheKey)) {
        shell.setBinding(null);
        getCaches().put(cacheKey, shell);
    }
} catch (Throwable t) {
    log.error("groovy script eval error. script: " + script, t);
}

return scriptObject;

3. 解决Binding的线程安全问题

private Map<String, Object> scriptCache = new ConcurrentHashMap<String, Object>();
...

Object scriptObject = null;
try {
    Binding binding = new Binding();
    binding.setVariable("context", this.context);
    binding.setVariable("clientInfo", clientInfo);
    binding.setVariable("params", params);
    binding.setVariable("data", data);

    Script shell = null;
    if (isCached(cacheKey)) {
        shell = (Script) getCaches().get(cacheKey);
    } else {
        shell = cache(cacheKey, script);
    }

    scriptObject = (Object) InvokerHelper.createScript(shell.getClass(), binding).run();

    // Cache
    if (!isCached(cacheKey)) {
        getCaches().put(cacheKey, shell);
    }
} catch (Throwable t) {
    log.error("groovy script eval error. script: " + script, t);
}

return scriptObject;

参考:

http://www.myexceptions.net/program/676961.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值