当前位置: 首页 > news >正文

记一次内存溢出的分析经历

说在前面的话

朋友,你经历过部署好的服务突然内存溢出吗?

你经历过没有看过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方法代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

publicbooleanread() {// try to read the frame size completely

if(this.state_ == AbstractNonblockingServer.FrameBufferState.READING_FRAME_SIZE) {

if(!this.internalRead()) {

returnfalse;

}

// if the frame size has been read completely, then prepare to read the actual time

if(this.buffer_.remaining() !=0) {

returntrue;

}

intframeSize =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?");

returnfalse;

}

// 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.");

returnfalse;

}

if(AbstractNonblockingServer.this.readBufferBytesAllocated.get() + (long)frameSize > AbstractNonblockingServer.this.MAX_READ_BUFFER_BYTES) {

returntrue;

}

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()) {

returnfalse;

}else{

if(this.buffer_.remaining() ==0) {

this.selectionKey_.interestOps(0);

this.state_ = AbstractNonblockingServer.FrameBufferState.READ_FRAME_COMPLETE;

}

returntrue;

}

}else{

this.LOGGER.error("Read was called but state is invalid ("+this.state_ +")");

returnfalse;

}

}


**说明:**
>MAX_READ_BUFFER_BYTES这个值即为对读取的包的长度限制,如果超过长度限制,就不会再读了/
>
>这个MAX_READ_BUFFER_BYTES是多少呢,thrift代码中给出了答案:

1

2

3

4

5

6

7

8

publicabstractstaticclassAbstractNonblockingServerArgs<TextendsAbstractNonblockingServer.AbstractNonblockingServerArgs<T>>extendsAbstractServerArgs<T> {<br>

publiclongmaxReadBufferBytes = 9223372036854775807L;

publicAbstractNonblockingServerArgs(TNonblockingServerTransport transport) {

super(transport);

this.transportFactory(newFactory());

}

}


>从上面源码可以看出,默认值居然给到了long的最大值9223372036854775807L。

所以thrift的开发者是觉得使用thrift程序员不够觉得内存不够用吗,这个换算下来就是1045576TB,这个太夸张了,这等于没有限制啊,所以肯定不能用默认值的。


步骤七.通信数据抓包分析

需要可靠的证据证明一个客户端通信的数据包的大小。



这个是我抓到包最大的长度,最大一个包长度只有215B,所以需要限制一下读取大小


步骤八:踏破铁鞋无觅处

在论坛中,看到有人用http请求thrift服务端出现了内存溢出的情况,所以我抱着试试看的心态,在浏览器中发起了http请求,

果不其然,出现了内存溢出的错误,和客户现场出现的问题一摸一样。这个读取内存的时候数量过大,超过了256MB。
> 很明显的一个问题,正常的一个HTTP请求不会有256MB的,考虑到thrift在处理请求的时候有反序列化这个操作。
>
> 可以做出假设是不是反序列化的问题,不是thrift IDL定义的不能正常的反序列化?
>
> 验证这个假设,我用Java socket写了一个tcp客户端,向thrift服务端发送请求,果不其然!java.lang.OutOfMemoryError: Java heap space。
> 这个假设是正确的,客户端请求数据不是用thrift IDL定义的话,无法正常序列化,序列化出来的数据会异常的大!大到超过1个G的都有。


步骤九. 找到原因

某些客户端没有正常的序列化消息,导致服务端在处理请求的时候,序列化出来的数据特别大,读取该数据的时候出现的内存溢出。

查看维护记录,在别的客户那里也出现过内存溢出导致服务端崩溃的情况,通过重新安装客户端,就不再复现了。

所以可以确定,客户端存在着无法正常序列化消息的情况。考虑到,客户端量比较大,一个一个排除,再重新安装比较困难,工作量很大,所以可以从服务端的角度来解决问题,减少维护工作量。

最后可以确定解决方案了,真的是废了很大的劲,不过也是颇有收获

http://www.jsqmd.com/news/1106739/

相关文章:

  • 耶鲁牛津剑桥等全球EMBA精英集聚复旦,拓数派董事长冯雷全英文授课“用Ontology实现零代码构建智能体”
  • 洗牙并非简单清洁:规范洁牙科普指南
  • Gemini AI工具全家桶深度应用指南
  • Java毕业设计-基于 SpringBoot 的线上手办周边商城系统的设计与实现 基于 SpringBoot 的动漫手办周边电商管理系统(源码+LW+部署文档+全bao+远程调试+代码讲解等)
  • LabVIEW让故障排查从“猜“变“算“
  • 2026年7月电锅炉厂家的选择应该考虑哪些因素?
  • 最近体验了一下 Visible Coding,AI 编程方式确实变了
  • SIGMOD 2025论文深度解读
  • AI 写了 500 行代码,上线后发现漏了 3 个接口、2 个路由、1 个菜单 —— 这套方法论让这种事再也没发生过
  • AI Agent实战:我用Gemini批量完成了《道德经》解读
  • 魔兽争霸3优化终极指南:如何免费解锁300帧高帧率游戏体验
  • 产品 | 《深渊世界》:潜入深海,开启生存冒险之旅!
  • 好用还专业!AI论文工具2026最新测评与推荐
  • 计算机Java毕设实战-基于 SpringBoot 的医院床位调度管理系统的设计与实现 基于 SpringBoot 的住院信息登记与运维系统【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • Java毕业设计-基于 SpringBoot 的医院住院部综合管理系统的设计与实现 基于 SpringBoot 的住院患者病房管控系统(源码+LW+部署文档+全bao+远程调试+代码讲解等)
  • CSDN-视频采集芯片选型指南
  • 量子修正黑洞热力学:模型构建与数值计算实践
  • 编写轻量级框架
  • 摩尔投票法:线性时间寻找多数元素的优雅算法
  • 基于LTC6903与PIC18的数字控制振荡器设计与实现
  • AI落地五大硬核挑战与可验证工程解决方案
  • CS2200-CP与PIC18F25K40高精度计时系统设计指南
  • python下载
  • AI交互数字人:智能一体机场景落地核心优势
  • 深度解析 diff-cover 架构设计:企业级代码覆盖率分析实战指南
  • 闭源大模型的信任红利正在耗尽,企业 AI 必将走向本地模型和开源 Agent——以端脑科技为例
  • 机器人技术全景指南:从机械躯壳到自主智能的进化之路
  • 通勤路上也能高效编程:5个Acode移动开发实战技巧让你随时随地写代码
  • 分布式、服务化的ERP系统架构设计
  • Day1 AI 到底淘汰谁?普通人最真实的生存真相