由于集群有流量的增长,以及开放新的服务出去,所以首先看了一下线程使用情况,未发现问题。
$JAVA_HOME/bin/jstack -l PID > jstack.out
grep -A 2 -B 5 -i "com.xxxxxx.xxxx" ./jstack.out 使用jstat查看GC情况发现PermGen将满,并且频繁触发FGC,虽然能够回收无效的类,但产生类的速度比FGC的效率还快,直接导致CPU使用率飙升。
ps -ef | grep java
$JAVA_HOME/bin/jstat -gcutil PID 1000 100 
	 
图1 PermGen区升到95%引发频繁FGC
遇到线上FGC问题常用的工具是用jmap & eclipse MAT来分析JVM内存使用情况了。网上资料很多,这里不在赘述,需要注意的一点是jmap是会触发"stop the world"的,所以好是拉出来,然后做dump操作。如果用eclipse MAT分析切记提前把eclipse大能使用的内存调整下,否则分析上G的dump文件会挂掉。
	 
图2 Dump中的类加载器情况
由于是PermGen区的泄漏,通过分析发现类加载部分有大量的GroovyClassLoader,这时想到有使用两个服务,内部实现是使用Groovy配置的方式(这里我用Groovy封装了一个服务提供的微框架)。但这两个服务其实使用的并不频繁,所以导致切换后的现象不是雪崩式的集群挂掉,而是慢慢的,逐步有机器load飙高告警。
定位到问题,事情就好办了,首先看一下处理Groovy加载执行的代码;然后为生成的类加一层对象缓存;由于脚本中用到了Binding上下文对象,为了线程安全性,调整执行时的方式。后问题得到解决。
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; 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; 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; 这次碰到的问题还是很具有典型性的,其中的思路和用到的工具可以阅读这两本JVM相关的书籍获取:《深入理解Java虚拟机》和《Java性能优化权威指南》。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。
