今天,自己开发的事件驱动的java大规模爬虫程序上线了几个新任务后突然异常。

异常:

   程序业务异常,经查看CPU利用率满,内存满,一直报OOM,目测有内存泄露。如下图所示,四核16G的内粗,CPU高达400%,内存使用了 6G,刚好爆满,JVM启动参数为:

java -server -Xmx6114M -Xms2048M -XX:+PrintGCDetails -XX:+PrintGCApplicationStoppedTime -Xloggc:/home/ubuntu/logs/spider/master/gc.log -XX:+PrintGCDateStamps -XX:SurvivorRatio= -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/ubuntu/heap/spider/master/dump -jar /home/ubuntu/apps/spider-service/spider-master/current

内存截图:

分析:

  首先找java进程为13419,找到该进程的什么线程使用最多,运行top -p 13419 -H  得到如下截图,一直观察得出,有四个线程一直占用100%的CPU;

  于是设法找到CPU的线程的调用栈,运行 jstack 13419 >表示 jstack.log 查看文件信息,发现没有对应线程的状态,也就找不到调用栈(注意,想找线程nid,可能需要转换16进制表示,因为线程nid在jstack.log中为16进制)

  

  想到这里,已经知道,该上面四个线程应该是GC线程,所以找不到,因此确定到堆文件分析;也就是初步断定没有死锁,而是堆溢出,但不一定是内存泄露;到这里,当然要去看看堆的GC情况了,运行jstat看看,如下图(S0为survive0区,S1为survive1区,而U代表使用,C代表总大小,E、O、M分别代表年轻代,年老代,Meta区,T为时间),不断的YGC而没有释放内存。

  在重量级的分析堆文件之前,我们可以运行jmap -histo 13419 >out.log查看对象的数量和持有内存分布,得到个大体情况;然后运行 jmap -dump:live,format=b,file=heap-dump.bin 13419  获得堆文件;其实jhat是可以分析堆文件的,但是我最终用了eclipse插件;

代码定位:

  最后定位到一个EventBus类持有5G大小,上千万个对象没处理,看看该类代码:

package com.mobisummer.spider.master.eventbus;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger; import com.mobisummer.spider.master.ctrlcentre.SpiderController;
import com.mobisummer.spider.master.ctrlcentre.monitor.Monitor;
import com.mobisummer.spider.master.ctrlcentre.monitor.Monitored;
import com.mobisummer.spider.master.eventbus.loop.BloEventGroup;
import com.mobisummer.spider.master.eventbus.loop.NloEventGroup;
import com.mobisummer.spider.master.eventbus.model.EventType;
import com.mobisummer.spider.master.eventbus.model.EventTask; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; /**
* 消息总线-双向队列
* @date 2019年1月9日
*/
@Component
public class EventBus implements Monitored { private static Logger log = LoggerFactory.getLogger("event");
private LinkedBlockingQueue<EventTask> taskRegistered = new LinkedBlockingQueue<>(); // wait to be handled
private ConcurrentLinkedQueue<EventTask> taskPool = new ConcurrentLinkedQueue<>(); // for reuse
private ConcurrentHashMap<Integer,AtomicInteger> eventCount =new ConcurrentHashMap<>(); @Autowired
SpiderController spiderController; public EventBus() {
Monitor.register(this);
} /**
* add event to queue tail
*
* @param eventType
* @param itemId
* @param object
*/
public void push(String itemId,int eventType, Object object) {
EventTask spiderEvent = retrieveEvent(eventType, itemId, object,spiderController);
try {
if(object==null){
System.out.println("");
}
if(spiderEvent.object==null){
System.out.println("");
}
taskRegistered.put(spiderEvent);
} catch (InterruptedException e) {
log.error("put item to queue cause an exception", e);
}
log.debug("add an event to tail of the queue, item id is {} , type is {}. the queue size is {} now", itemId, EventType
.getDescription(spiderEvent.eventType), taskRegistered.size());
} private EventTask retrieveEvent(int eventType, String itemId, Object object,EventHandler handler) {
EventTask spiderEvent = taskPool.poll();
if(spiderEvent == null) {
spiderEvent = new EventTask(eventType, itemId, object,handler);
} else {
spiderEvent.eventType = eventType;
spiderEvent.itemId = itemId;
spiderEvent.object = object;
spiderEvent.handler =handler;
}
return spiderEvent;
} /**
* 循环消费需要处理的事件
*/
public void loop() {
while (true) { }
} public void releaseEvent(EventTask spiderEvent) {
spiderEvent.object = null;
taskPool.add(spiderEvent);
} public static void eventError(Integer eventType, String itemId, Object object,Exception e){
String errDesc= "handle event cause an exception ======" +e.toString()+"======";
if(eventType!=null){
errDesc = errDesc+eventType ;
}
if(itemId!=null){
errDesc =errDesc+itemId;
}
if(object!=null){
errDesc =errDesc+object.toString();
}
log.error(errDesc);
} @Override
public Object getHealthIndex() {
String desc ="";
for(Integer event :eventCount.keySet()){
desc =desc+"\n事件:"+EventType.getDescription(event)+" 该事件共产生次数"+eventCount.get(event);
}
return desc;
} private void countEvent(int eventType){
AtomicInteger count = eventCount.get(eventType);
if(count==null){
count = new AtomicInteger();
eventCount.put(eventType,count);
}else {
count.incrementAndGet();
}
}
}

  这是一个事件总线,可以看出因为堆溢出,不断的GC,处理事件的是一个单线程根本没有被调度的机会,或许已经挂了!堆积事件不处理,这个不是内存泄露,只是系统崩盘了,最后重构调度代码,重视调度,构建调度抽象:

重构:

  先把事件分类,绑定事件和控制器,做成状态机,然后通过EventBus分配给阻塞的Blo执行或者非阻塞的Nlo执行,而且各自分配Eventloop去执行;重构后上线,加大压测;能承受量加大了!不过系统还是要有机制拒绝服务才行的,这个就是后话了。   

记一次JVM故障排除的更多相关文章

  1. JVMTI 中间JNI系列功能,线程安全和故障排除技巧

    JVMTI 中间JNI系列功能,线程安全和故障排除技巧 jni functions 在使用 JVMTI 的过程中,有一大系列的函数是在 JVMTI 的文档中 没有提及的,但在实际使用却是很实用的. 这 ...

  2. ORA-4031 错误故障排除与诊断[视频] (Doc ID 2016002.1)

    Copyright (c) 2019, Oracle. All rights reserved. Oracle Confidential.     ORA-4031 错误故障排除与诊断[视频] (Do ...

  3. spark——故障排除

    故障排除一:控制reduce端缓冲大小以避免OOM 在Shuffle过程,reduce端task并不是等到map端task将其数据全部写入磁盘后再去拉取,而是map端写一点数据,reduce端task ...

  4. 学习笔记:CentOS7学习之十八:Linux系统启动原理及故障排除

    目录 学习笔记:CentOS7学习之十八:Linux系统启动原理及故障排除 18.1 centos6系统启动过程及相关配置文件 18.1.1 centos6系统启动过程 18.1.2 centos6启 ...

  5. linux下抓取tomcat相关内存、线程、文件句柄等快照,用于故障排除。

    以下脚本推荐放在定时任务里,写好cron表达式,在不影响业务系统的情况下dump一些信息分析系统性能瓶颈以及故障排除. 因为每次dump的时候jvm会暂停(几秒到几十秒不等).所以在生产系统使用时慎用 ...

  6. spark 性能优化 数据倾斜 故障排除

    版本:V2.0 第一章       Spark 性能调优 1.1      常规性能调优 1.1.1   常规性能调优一:最优资源配置 Spark性能调优的第一步,就是为任务分配更多的资源,在一定范围 ...

  7. 使用JDK工具进行Java服务器应用程序故障排除

    Java性能调优指南–有关提高Java代码性能的各种技巧. 最近又学到了很多新知识,感谢优锐课老师细致地讲解,这篇博客记录下自己所学所想. 1. 介绍 在Java世界中,我们大多数人习惯于在Java应 ...

  8. 坑爹坑娘坑祖宗的87端口(记一次tomcat故障排查)

    原贴如下 坑爹坑娘坑祖宗的87端口(记一次tomcat故障排查) 虽然我用的是PHPstudy部署的dedecms,还是一样栽倒这个坑里了. 总结经验:本地测试使用8000~9000的端口比较安全.

  9. 理解 OpenStack + Ceph (7): Ceph 的基本操作和常见故障排除方法

    本系列文章会深入研究 Ceph 以及 Ceph 和 OpenStack 的集成: (1)安装和部署 (2)Ceph RBD 接口和工具 (3)Ceph 物理和逻辑结构 (4)Ceph 的基础数据结构 ...

随机推荐

  1. 微信支付errcode:40163,code been used,错误小结

    1.配置时注意,支付平台中的支付授权目录, 注意大小写. 昨天碰到的问题,就是自己跳转时,路径写的全小写.跳转支付页面也能跳转过去,但是log中总是调用两次code,报40163错误.后改成和公总号支 ...

  2. ubuntu server 16.04 安装过程中提示无法安装busybox-initramfs

    这个问题在安装desktop版本时是不会出现的,只有server才有这个问题. 出现这个问题与硬件平台无关,不管是虚拟机还是物理机都会出现,解决的办法是在安装开始界面选择English,后面Langu ...

  3. C++入门篇十三

    常对象: const  Person p1; 不可以调用普通成员函数,除非前面加了函数前面加了const可以调用常函数在对象之前加入const修饰 const Person p1; 常函数:void ...

  4. Js获取客户端用户Ip地址

    利用搜狐查询接口查询Ip地址: <!DOCTYPE html> <html> <head> <meta charset="utf-8" / ...

  5. 2-1、FileBeat入门

    FileBeat入门 要开始使用Filebeat设置,请安装并配置相关产品: 用于存储和索引数据的Elasticsearch. 用户界面的Kibana. 用于解析和增强数据的Logstash(可选). ...

  6. js中valueOf方法的使用

    今天一位刚毕业的同事问了我一个问题,为什么这段代码执行结果是-1.代码如下: var o = { valueOf: function(){ return -1; } }; o = +o; 当时我也是懵 ...

  7. Jenkins编辑或替换All view

    为什么我不能编辑“All”view? 这是因为它的类型是“All”而不是“List”,并且“All”类型是不可编辑的.你只能有一个“All”类型的view. 如果你想编辑这个View,你将不得不创建一 ...

  8. 末学者笔记--shell编程上 1 玄

    Shell 编程 [内容简列]: 1.shell简介 2. shell分类 3. 查看shell 4. 第一个shell脚本 5. shell编程常用命令 5.1 grep 5.2 cut 5.3 s ...

  9. Zabbix(二)

    zabbix 监控第一台服务器 https://blog.51cto.com/5001660/2136303 一.搭建一台测试服务器 1.安装一台centos7操作系统 配置网络: vim /etc/ ...

  10. jq修改hover样式

    $("#resTree").hover(function(){ $(this).css("background-color","yellow" ...