# 背景

最近开发了一个特性,关于 websocket 的功能,代码量直接给感到了 5k, 联调都好了,程序就挂在服务器上,然后就没管了,今天发现这个服务器,卡卡的,一看 CPU 占用 500%, 惊了

# 定位

# 先看哪个线程这么吃 CPU

top -Hp pid, 这个是查看线程,直接 top 显示的都是进程
然后取 CPU 占用最高的一个,printf “% x\n” 线程号,
再用 jstack pid > stack.txt
在 stack.txt 里面检索一下,发现是 GC 的线程,看了 CPU 占用最高的几个线程,都是 GC 的,然后突然发现端倪,为什么 Thread-XXX 的线程这么多呢,有几千个,看栈帧,看线程名,只能知道是系统龙出来的,但是我自己弄的线程都是有给命名的,感觉很奇怪

# 先看 GC 情况,判断是一直在 GC

用 jstat -gcutil pid 1000 10, 发现 FGC 很少,但是 YGC 却来得很多,还一直再增长,这种情况,没有 FGC, 一直 YGC, 那多半,并没有大对象,如果是大对象就直接进老年代,触发 FGC 了

# 查看 heapdump

本来准备先 jmap -heap pid 的,结果上传到服务器的 jdk 和 jre 版本不支持,索性直接 jmap -dump:format=b,file=home/xxx/pid.dump, 推荐用 mat(Memory Analyzer Tool), visualvm, 还是太简陋了,不容易分析,大概看了一遍,没有自己封装的对象在,
然后我又检索了一下 websocket 的类名,发现 2000 多个,哦和,问题肯定出现在 new WebscoketClient 的地方,走读了一下代码,发现连接不通的时候没有 close, 导致 GC 一直不把他当成垃圾

# 源码解析

WebSocketClient 中有两个线程对象,
private Thread writeThread;
private Thread connectReadThread;
所以现象中有线程数爆炸,且命名也是系统给的,让人混淆
再说为什么会有这么多自定义的 WebSocketClient 对象,因为代码终有重连机制
改正也简单,加上 连接失败,机上 close 就可以了


public abstract class WebSocketClient extends AbstractWebSocket implements Runnable, WebSocket {

/**
* The URI this channel is supposed to connect to.
*/
protected URI uri = null;

/**
* The underlying engine
*/
private WebSocketImpl engine = null;

/**
* The socket for this WebSocketClient
*/
private Socket socket = null;

/**
* The SocketFactory for this WebSocketClient
*
* @since 1.4.0
*/
private SocketFactory socketFactory = null;

/**
* The used OutputStream
*/
private OutputStream ostream;

/**
* The used proxy, if any
*/
private Proxy proxy = Proxy.NO_PROXY;

/**
* The thread to write outgoing message
*/
private Thread writeThread;

/**
* The thread to connect and read message
*/
private Thread connectReadThread;

/**
* The draft to use
*/
private Draft draft;

/**
* The additional headers to use
*/
private Map<String, String> headers;

/**
* The latch for connectBlocking()
*/
private CountDownLatch connectLatch = new CountDownLatch(1);

/**
* The latch for closeBlocking()
*/
private CountDownLatch closeLatch = new CountDownLatch(1);

/**
* The socket timeout value to be used in milliseconds.
*/
private int connectTimeout = 0;

/**
* DNS resolver that translates a URI to an InetAddress
*
* @see InetAddress
* @since 1.4.1
*/
private DnsResolver dnsResolver = null;