背景
收到一出产环境报警音讯,一个集群的CPU利用率直接飙升到了100% 领导说让我处理,可是我还不知道这个集群是干啥用的。。
找问题
看监控
先看下监控看下能不能看到有什么发现,发现这个集群消费MQ音讯的数量突增,有几个尖刺状的流量现已达到了每分钟8W+, 很有可能是尖刺流量形成的这个问题,可是在8:30之后流量现已趋于缓和,可是CPU仍是一直100%并没有降下去。
检查jvmGC状况
所以登录服务器看下jvm的运行状况。
通过 jstat -gcutil
命令检查服务的GC状况,发现现已进行了500屡次的FullGC。
要害点在于图中的 534 -> 535次的FullGC内存一点都没有被收回下去,这就很可怕了。
arthas检查服务全体状况
所以想用arthas检查下是否是内存分配有问题到这的内存无法收回,
如图:发现jvm的非堆内存的利用率现已满了,那么很有可能是这个问题导致的。
所以想办法调整下这个内存的巨细,翻开项目中配置的启动命令发现现已设置了非堆内存的巨细,可是这儿并没有收效
nohup java -Xms2g -Xmx2g -Xmn1g -Xss1024K -XX:PermSize=512m -XX:MaxPermSize=512m
启动参数长这个姿态,其中 -XX:PermSize=512m
便是设置非堆内存巨细的。可是没有收效。。。。
为什么没收效呢???
jdk7->jdk8内存结构的调整
我们都知道7到8的改动很大,其中一块改动便是jdk8将以前的永久代。
也便是说**_-XX:PermSize_**
这个参数在jdk8中现已没有用了,可是这个陈旧的项目并没有人去修正这个参数。
先调整下这个参数试试。
关于永久代和元空间的介绍:
www.cnblogs.com/paddix/p/53…
cloud.tencent.com/developer/a…
分析堆内存
内存无法收回一般是由内存泄漏,或许有大对象无法收回形成的,尽管上面找到一处问题,可是并不能很好的解释为什么内存无法被收回。 分析下堆内存,
- 运用
jmap -dump:live,format=b,file=heap.hprof
命令将堆内存的快照导出,下载下来。 - 运用
jvisualvm
jdk自带的工具分析下内存状况
类的散布状况,最大的 cahr[] string
这个没什么问题,第三名和第四名就有问题了,
这两个一个是 阻塞行列、一个是业务代码中的一个内部类。
一个内部类怎么会占用这么大的比重,看下代码
ExecutorService executorPool = Executors.newFixedThreadPool(numberOfCore);
RateLimiter handleLimit = RateLimiter.create(20, 10, TimeUnit.SECONDS);
ESBClient client = new ESBClient(key_esb);
client.setReceiveSubject(new ESBSubject(INFO_DEL_SUBJECTID, INFO_DEL_CLIENTID,SubMode.PULL));
client.setReceiveHandler(new ESBReceiveHandler() {
@Override
public void messageReceived(final ESBMessage msg) {
try {
final String msgStr = new String(msg.getBody(), "UTF8");
executorPool.execute(new Runnable(){
public void run() {
try {
handleLimit.acquire();
handleDelMsg(msgStr);
} catch (Exception e) {
e.printStackTrace();
}
}
});
} catch (Exception e) {
}
}
});
大家一同看下这端代码有没有什么问题,
- 第一点,这儿运用了一个一定不能运用的线程池创立办法,
Executors.newFixedThreadPool
这个办法创立出来的线程池里边的使命行列是个无界行列。 - 第二点这儿用到了限流,
RateLimiter
这儿应该是防止流量过大下游扛不住。
可是为什么要在使命里边限流呢?在线程使命里边限流这个使命放到线程池中,线程在履行使命的时候就会等候 拖慢了整个线程池的运行速度,并发量高的时候大量的使命被放到行列中,而行列又是个无界行列 一下就会把jvm的内存打满,而且这些使命都是被线程池引用的状况无法收回。 这样jvm就会一直进行GC,现已在行列中的使命拿不到CPU资源无法履行,GC又无法收回内存导致了整个集群挂掉。
问题修正
除了修正元空间的启动参数,这儿还需要修正代码如下
private static final ExecutorService executorPool = new ThreadPoolExecutor(numberOfCore, numberOfCore * 2, 10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1024), new DefaultThreadFactory("custom"), new ThreadPoolExecutor.CallerRunsPolicy());
RateLimiter handleLimit = RateLimiter.create(20, 10, TimeUnit.SECONDS);
ESBClient client = new ESBClient(key_esb);
client.setReceiveSubject(new ESBSubject(INFO_DEL_SUBJECTID, INFO_DEL_CLIENTID,SubMode.PULL));
client.setReceiveHandler(new ESBReceiveHandler() {
@Override
public void messageReceived(final ESBMessage msg) {
try {
handleLimit.acquire();
final String msgStr = new String(msg.getBody(), "UTF8");
executorPool.execute(new Runnable(){
public void run() {
try {
handleDelMsg(msgStr);
} catch (Exception e) {
e.printStackTrace();
}
}
});
} catch (Exception e) {
}
}
});
- 首先将限流器移到提交使命的时候限流。
- 修正线程池的参数,有界行列、当行列满了调用者履行使命。由于我们这儿消费MQ运用的是PULL模式,所有当使命消费不过来的时候就不会去拉音讯了,所以选用这个
CallerRunsPolicy
策略
后续重视
内存和GC恢复正常