由于 cmd 引发的血案
前言
给教务处做一个工作量审核的 web 项目,前几天完成后部署在机房电脑上进行测试,但是测试的时候出现了 Bug,会存在一个用户登录的时候,整个应用卡死,所有人都无法登陆,因为登陆模块是我写的,我当时就慌了(并不),然后赶紧去机房看看到底是啥情况
FixBug
其实我首先想到的就是网络或者数据库的问题,但是看了我控制台的输出,发现代码其实根本就还没到数据库层!所以先排除了数据库的问题
然后我就想会不会是 GC 的问题,然后我打开了 jvisualvm
,准备查看当前的堆占用和 GC 情况,但是他们之前重启了服务,现在是正常的,看不出来什么,所以我们需要复现这个 Bug,结果我们几个人在哪里搞了半天死活复现不了😂
在经过我们一个小时多小时坚持不懈的尝试下,终于复现了!
页面 hang 住,所有人都无法登陆,后台也并没有任何的错误信息,然后我赶紧去看了下 jvisualvm
发现堆并没有任何变化,GC 也并没有发生!而且更诡异的是其他的已经登陆的人是可以正常的操作的!只是卡住了登陆的人,所以 GC 的问题也排除了
在 dump 出线程快照后终于发现了问题所在
图中的这条线程卡在了 PrintStream.println()
上,而这个是我在 Service 打印的 log 信息,为什么会在这里卡住???这不科学啊,然后我看了 其他 tomcat 的工作线程,发现还有好几个都是BLOCK
状态,都在等 [0x0000004df8afd000]
这把锁,这个锁被另一个 tomcat 的工作线程 http-bio-80-exec[2]
所持有,而且它并不是BLOCK
状态,而是 RUNNABLE
状态,这个线程正在执行 java.io.FileOutputStream.writeBytes()
方法,我们去看看println
的源码
/**
* Prints a String and then terminate the line. This method behaves as
* though it invokes <code>{@link #print(String)}</code> and then
* <code>{@link #println()}</code>.
*
* @param x The <code>String</code> to be printed.
*/
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
可以看出在 println 方法确实是加了锁的,锁的对象就是当前的 PrintStream 实例对象,而占用这个锁的对象的线程则正在执行下面这个方法,是个本地方法我们看不到底层的细节
/**
* Writes a sub array as a sequence of bytes.
* @param b the data to be written
* @param off the start offset in the data
* @param len the number of bytes that are written
* @param append {@code true} to first advance the position to the
* end of file
* @exception IOException If an I/O error has occurred.
*/
private native void writeBytes(byte b[], int off, int len, boolean append)
throws IOException;
无奈,借助搜索引擎,果然查到了同样的问题
一个 RUNNABLE 状态的线程 hang 在了 java.io.FileOutputStream.writeBytes 方法上
当然原问题是来自 StackOverflow
问题的根本原因:在 CMD 窗口点击了黑框之后,控制台就会被暂停!mark
,进入编辑模式,之后向控制台的输入缓冲区会被很快填满,然后正在输出的线程就会 hang 住,但是正在输出的线程也并不会BLOCK
,状态仍然是RUNNABLE
所以就很诡异,也就是上面所描述的情况,当你这个时候在 CMD 状态下按一下回车或者其他的键释放 console,退出编辑模式!mark
这个线程又会继续往下执行,要解决这个问题可以调整 cmd 的设置,我这里其实无所谓,因为后面并不会在 win 上运行
至此问题就基本解决了(其实都不算问题),没想到还会被 cmd 给坑一把,不过增长了一点排查问题的能力也还是不错的😁
总结
最大的收获就是: 遇到 bug 不要先急着重启逃避问题,得面对它,因为有些 bug 完全就是偶然性的,一旦放过,要想复现可能会极其困难,这次还算运气好,最终复现了,不过当时如果他们保护了现场后面就不会花几个小时去复现问题(确实大家也都经验不太足),也算是吃一堑长一智了 hahaha ~~