一个线上的问题,随着时间推移,线程没有正常关闭导致的线程泄漏,现象是服务器内存趋于百分之百。
创新互联公司于2013年成立,是专业互联网技术服务公司,拥有项目做网站、网站建设网站策划,项目实施与项目整合能力。我们以让每一个梦想脱颖而出为使命,1280元西吉做网站,已为上家服务,为西吉各地企业和个人服务,联系电话:13518219792解决办法: 1. 借助指令分析。使用 pslist -dmx pid配合jstack -l pid ,pslist结果如下图。发现随着时间推移(3分钟增加一个),线程会越来愈多。而且线程的内容会逐渐增加。可以将问题定位到定时任务(有个定时任务是3分钟执行一次),初步确定为由定时任务引发的问题。
2.查看jstack结果。如下图,发现新增的线程都有个共性,都是由一个很陌生的类com.mashape.unirest.http.utils.SyncIdleConnectionMonitorThread 创建,于是在项目中全局搜这个类,通过搜com.mashape.unirest,找到了代码所在位置。
3. 分析代码。细看代码,发现这是一个类似于发送http请求的工具类,只有这两块代码。
这里有两种解决办法, 第一是查看源码,分析原因,第二种就是直接调试代码。
推荐用第二种(别问为什么,问就是因为简单、快),我当时采用的是初中生物学到的控制变量法进行的测试,保持其他条件不变,①注掉第104和105-112行相关代码、②只注释第104行代码、③只注释105-112行代码。这三种情况测试完以后发现①、②两种情况不会复现线程溢出的问题,于是定位到问题出在设置超时时间这行代码上(104行)。
至此,问题就找到了,我们只需要把104这行代码初始化一次,比如放进静态代码块,保证只会在类被加载的时候初始化一次,而不是每次执行到这里就初始化,这样就可以解决每次都会开启线程的问题了。
4. 根本原因。当然,作为一名优秀的程序员,没有查看源码的好习惯明显是不合格的(我怎么这么不要脸 - -!),通过分析源码,截图如下:
我发现Unirest.setTimeouts()这个方法内部有一个refresh()的方法,点进去看到有这么一坨代码:
其中最长的一行显示不下了,我粘在这里:
RequestConfig clientConfig = RequestConfig.custom()
.setConnectTimeout(((Long)connectionTimeout).intValue())
.setSocketTimeout(((Long)socketTimeout).intValue())
.setConnectionRequestTimeout(((Long)socketTimeout).intValue())
.setProxy(proxy)
.build();
这块代码大概意思是,用我们之前setTimeouts方法参数里穿进去的超时时间初始化客户端的配置信息,之后将配置信息写入连接池并初始化连接池的一些信息,最后会开启一个名为SyncIdleConnectionMonitorThread的线程来处理这个连接池,看线程名字的意思,是一个监测闲置链接的线程,就是清理资源的,看一看里面的东西:
他用了个死循环不断监视没有关闭的线程,并获取当前线程对象的对象锁,wait方法没啥意义,就是为了防止循环太快搞死cup,每次都等5s再执行。之后就是关闭资源的操作,然后继续循环,直到线程关闭时,再执行isInterruptes()方法就会报错抛异常,然后return退出线程。看起来好像没什么问题,疑问就是我并不知道他会在哪里停止当前线程,毕竟这个线程不会自己关闭,这样就会一直循环下去,但是鬼知道哪里会停止当前线程。
到这里还是没有发现为什么,问题到底出在哪里?分析到这里的时候,我决定再细看一波代码,当然还是从setTimeouts这里看起,终于,在一行一行看源码以后,粗心的我发现了问题,就在线程类的构造方法里,有这么一行不起眼的代码:super.setDaemon(true); 于是Google找了Thread.setDaemon(true)是啥意思:通过Thread.setDaemon(true)设置为守护线程,在没有用户线程可服务时会自动离开。这里还很贴心的给出了实例,看样子是说,只要主线程不死,守护线程就会一直存在。也就是说定时任务执行一次,就会创建一个守护线程。
我和谐他和谐的和谐,这也太坑了吧!!
5. 总结这次主要是用了pslist 和jstack 指令查看线程相关信息(linux系统可以使用top配合jstack,也可以借助其他插件,比如ali的arthas,用起来挺不错),发现了异常线程,并通过这个信息定位到出错的代码位置,这时就要考验分析代码的基本功了。
辛辛苦苦分析了这么多,居然是这么个问题。可见,在使用别人的工具的时候,一定要好好查看api,或者多看几个相关资料再下手,避免产生意想不到的问题。
另外,类似配置信息的这种东西,还是不要频繁初始化的好,如果api里没有详细介绍使用细节,可以看一看源码,避免出错(时间充裕的情况下)。
参考:
JAVA Thread.setDaemon用法
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧