说在前面的话
伴侣,你经验过陈设好的处事溘然内存溢出吗?
你经验过没有看过Java虚拟机,来办理内存溢出的疾苦吗?
你经验过一个BUG,百思不得其解,头发一根一根脱落的烦恼吗?
我知道,你有过!
可是我照旧要来说说我的故事………………
配景:
有一个项目做一个系统,分客户端和处事端,客户端用c++写的,用来收集信息然后传给处事端(客户端的数量照旧较量多的,正常的有几千个),
处事端用Java写的(带打点页面),属于RPC模式,中间的通信框架利用的是thrift。
thrift许多利益就不多说了,昆山软件开发,它是facebook的开源的rpc框架,主要是它可以或许跨语言,序列化速度快,可是他有个不讨喜的处所就是它必需用本身IDL来界说接口
thrift版本:0.9.2.
问题定位与阐明
步调一.劈头阐明
客户端无法毗连处事端,查察处事器的端口开启状况,处事端口并没有开启。于是启动处事端,启动几秒后,处事端瓦解,反复启动,处事端依旧在启动几秒后瓦解。
步调二.查察处事端日志阐明
阐明得知是因为java.lang.OutOfMemoryError: Java heap space(堆内存溢出)导致的处事瓦解。
客户端汇集的主机信息,主机计策都是放在缓存中,大概是因为缓存较大造成的,可是通过日志可以看出是因为Thrift处事抛出的堆内存溢出异常与缓存巨细无关。
步调三.再次阐明处事端日志
可以发明每次抛出异常的时候城市陪伴着几十个客户端在向处事端发送日志,往往在发送几十条日志之后,处事瓦解。可以假设是不是堆内存配置的太小了?
查察启动参数设置,最大堆内存为256MB。修改启动设置,启动的时候分派更多的堆内存,改成java -server -Xms512m -Xmx768m。
功效是,能僵持多一点的时间,依旧会内存溢出处事瓦解。得出结论,一味的扩大内存是没有用的。
**为了证明结论是正确的,做了这样的尝试:**
> 内存配置为256MB,昆山软件开发,在公司处事器上陈设了处事端,利用Java VisualVM长途监控处事器堆内存。
> 模仿客户现场,昆山软件开发,注册3000个客户端,利用300个线程同时发送日志。
> 功效和想象的一样,没有呈现内存溢出的环境,如下图:
> 上图是Java VisualVM长途监控,在压力测试的环境下,没有呈现内存溢出的环境,256MB的内存必定够用的。
步调四.回到thrift源码中,查找要害问题
处事端回收的是Thrift框架中TThreadedSelectorServer这个类,这是一个NIO的处事。下图是thrift处理惩罚请求的模子:
**说明:**
>一个AcceptThread执行accept客户端请求操纵,将accept到的Transport交给SelectorThread线程,
>AcceptThread中有个balance平衡器分派到SelectorThread;SelectorThread执行read,write操纵,
>read到一个FrameBuffer(封装了要领名,参数,参数范例等数据,和读取写入,挪用要领的操纵)交给WorkerProcess线程池执行要领挪用。
>**内存溢出就是在read一个FrameBuffer发生的。**
步调五.细致一点描写thrift处理惩罚进程
>1.处事端处事启动后,会listen()一直监听客户端的请求,当收到请求accept()后,交给线程池去处理惩罚这个请求
>2.处理惩罚的方法是:首先获取客户端的编码协议getProtocol(),然后按照协议选取指定的东西举办反序列化,接着交给业务类处理惩罚process()
>3.process的顺序是,**先申请姑且缓存读取这个请求数据**,处理惩罚请求数据,执行业务代码,写响应数据,**最后排除姑且缓存**
> **总结:thrift处事端处理惩罚请求的时候,会先反序列化数据,接着申请姑且缓存读取请求数据,然后执行业务并返反响应数据,最后请求姑且缓存。**
> 所以压力测试的时候,thrift机能很高,并且内存占用不高,是因为它有自负载调理,利用NIO模式缓存,并利用线程池处理惩罚业务,每次处理惩罚完请求之后实时排除缓存。
步调六.研读FrameBuffer的read要领代码
可以解除去没有实时排除缓存的大概,偏向明晰,极大的大概是在申请NIO缓存的时候呈现了问题,回到thrift框架,查察FrameBuffer的read要领代码:
public boolean read() { // try to read the frame size completely if (this.state_ == AbstractNonblockingServer.FrameBufferState.READING_FRAME_SIZE) { if (!this.internalRead()) { return false; } // if the frame size has been read completely, then prepare to read the actual time if (this.buffer_.remaining() != 0) { return true; } int frameSize = this.buffer_.getInt(0); if (frameSize <= 0) { this.LOGGER.error("Read an invalid frame size of " + frameSize + ". Are you using TFramedTransport on the client side?"); return false; } // if this frame will always be too large for this server, log the error and close the connection.
if ((long)frameSize > AbstractNonblockingServer.this.MAX_READ_BUFFER_BYTES) { this.LOGGER.error("Read a frame size of " + frameSize + ", which is bigger than the maximum allowable buffer size for ALL connections."); return false; } if (AbstractNonblockingServer.this.readBufferBytesAllocated.get() + (long)frameSize > AbstractNonblockingServer.this.MAX_READ_BUFFER_BYTES) { return true; } AbstractNonblockingServer.this.readBufferBytesAllocated.addAndGet((long)(frameSize + 4)); this.buffer_ = ByteBuffer.allocate(frameSize + 4); this.buffer_.putInt(frameSize); this.state_ = AbstractNonblockingServer.FrameBufferState.READING_FRAME; } if (this.state_ == AbstractNonblockingServer.FrameBufferState.READING_FRAME) { if (!this.internalRead()) { return false; } else { if (this.buffer_.remaining() == 0) { this.selectionKey_.interestOps(0); this.state_ = AbstractNonblockingServer.FrameBufferState.READ_FRAME_COMPLETE; } return true; } } else { this.LOGGER.error("Read was called but state is invalid (" + this.state_ + ")"); return false; } }
**说明:**
>MAX_READ_BUFFER_BYTES这个值即为对读取的包的长度限制,假如高出长度限制,就不会再读了/