zoukankan      html  css  js  c++  java
  • 脚本引擎栈指针误操作引起内存泄露

    最近,为了确定目标部署机器的配置,我们对服务器进行了玩家登录的压力测试。在E7500 cpu,3G内存的开发机上,同一时间登录人数不能超过20人。以0.01秒间隔登录,测试人数5000人,登录时一个核心在30%-60%之间波动,另一个核心比较空闲。稳定后,CPU平均在5%一下(玩家只是登录后保持心跳),峰值内存在2.3G以内。但比较诡异的是,当玩家全部断开后,服务器的内存还是没有降下来。我觉得,可能是发生内存泄露了。

    发现这个问题后,我排查了底层对玩家user object的管理。user有两个部分组成,一个是外部包裹的一层网络统计相关的信息,另一个是内部的lpc object。外部的user有online_user_tbl这个哈希表负责管理,提供了遍历方法、总数统计及获取所有对象的方法。而实际数据的存放,则是在all_users里面,这个是一个大型数组,启动时按配置设定的最大在线人数进行预分配。

    通过dumpallobj()接口,初步发现内存泄露时,user对象虽然释放缓慢,但是在几分钟之内,已经全部释放完毕。因此排除了user对象的泄露。再加上预分配的all_users列表,大小是可以估算出来的,大概400M的样子,不可能引起那么大的问题。于是又通过memory_info(), 打印出lpc vm各种string, mapping, object的内存占用,发现还是没有异常。跟老大商量过后,他认为是内部做了内存池之类的结构,证据是内存不会无限疯长,只要没有活跃玩家,峰值就停留在2.3G左右。由于没有头绪,只好暂时放下。

    昨天,为了测试网关服务器的转发性能,我便写了个ping pong测试,大约是3Mb/s,1000包/s的样子。CPU占用有点高,一个核心占了50%。诡异的是,做ping pong测试没有涉及具体逻辑,但是内存一直在不停地涨。跟老大反映后,他认为是最近新加入的脚本层socket有问题。于是,花了大半天的时间,仔细审核那段代码。的确发现了有一处内存泄露,在某一处错误检测里,会直接return掉,没有处理中间分配的临时变量。不过那个条件分支没有跑到,所以内存泄露应该是别的地方。

    今天,又特意写了黑洞测试来考察脚本层socket。在只是打数据到远端服务器,不需要序列化,不需要处理逻辑的情况下,服务器内存占用相当稳定,看来可以排除lpc socket了。ping pong的逻辑只有100行不到,那剩下来的应该就是序列化数据和反序列化数据的地方了。当时,出于通用协议的考虑,内部服务器之间的通信,没有具体定协议,只是以req和resp两条协议做支撑,参数序列化为json字符串进行发送。没想到,单独序列化和单独反序列化都没有内存泄露。我原以为是序列化/反序列化过程中,对临时生成的字符串没处理好,造成内存泄漏的。

    后来,和老大一道,审读json序列化和反序列化的代码,发现栈顶指针操作有点异常。对于一般的脚本层调C层efun,会用宏SET_ALL_ARGS获得参数的个数和偏移地址,在处理完参数转换后,再通过POP_ALL_ARGS清除所有栈顶的所有参数(参数的引用计数减1)。当有返回值时,将返回值压入栈顶。没有返回值时,压入空值。但是,反序列化的函数,只是暴力的将栈顶指针指向自己生成的返回值,没有对传入的参数做任何处理。就是这个地方,导致了参数的内存泄漏。其实我早该想到的,不是序列化过程中的泄漏,也不是某一单过程的泄露,那就应该是传参出问题了。。。以后要统一规范,用对应的宏来正确处理栈指针和脚本层传来的参数。

  • 相关阅读:
    moss文档浏览次数统计
    C#中方法参数的四种类型
    [记录] JavaScript 中的深浅拷贝(克隆)
    [记录] JavaScript 中的事件分类
    [记录] JavaScript 中的数组操作
    [记录] JavaScript 中的事件(Event对象、事件源对象、事件流、事件绑定)
    [记录] JavaScript 中的try..catch 详细的错误信息
    [题目] JavaScript 练习题目(一) [020]
    [记录] JavaScript 中的this和call()、apply()方法
    [记录] JavaScript 中的对象操作和包装类
  • 原文地址:https://www.cnblogs.com/Lifehacker/p/find_out_memory_leak_by_stress_testing.html
Copyright © 2011-2022 走看看