自从用了 arthas 这个工具,终于在 java 开发中体会到了 php 的 1/5 的快乐了。
前几日晚上开会途中,收到报警信息,提示某个 服务内存占用过高。
dump 一台机器的堆内存分析了下,发现堆内存里占用内存最多的实例对象是 org.apache.http.impl.conn.PoolingHttpClientConnectionManager
,既然实例数一直上涨,肯定有不停的初始化调用的地方,直接使用arthas stack
查看这个对象的调用栈
查看源码发现在com.aliyun.oss.common.comm.DefaultServiceClient
里初始化客户端的时候,会先创建一个连接池,然后将连接池其放入到一个守护线程 IdleConnectionReaper
里面进行连接的回收管理
空闲连接回收线程,会将每次创建的 OSS 客户端对应的 org.apache.http.impl.conn.PoolingHttpClientConnectionManager
放入 ArrayList
不主动关闭客户端com.aliyun.oss.OSSClient#shutdown
的情况是不会调用removeConnectionManager
方法的。
如果每次初始化之后都要com.aliyun.oss.OSSClient#shutdown
,则违背了设计连接池的初衷。
小结
- 所以最好是使用单例模式,之所以设计
ArrayList
来追加org.apache.http.impl.conn.PoolingHttpClientConnectionManager
,应该是考虑存在多个不同的OSSClient
,比如多个账号或者多个oss
地址。 - 如果下次遇到类似情况,我可能会优先尝试
jmap -histo $pid|more
的方式看看有没有比较明显泄露的实例对象,因为这样操作比 dump 堆内存下来,离线分析要来的快;当然另一方面也不够直观。 - 自从用了 arthas 这个工具,终于在 java 开发中体会到了 php 的 1/5 的快乐了。