电脑最大内存4g
十年前512M内存的电脑用得飞快,而现在4G的还不够用,是因为软件太大还是程序优化的程度比以前降低了,抑或是硬件的问题? 我们一起思考下!先看几张图这是10年前的WindowsWindows Vist...
2024.11.14某线上运行的C++服务,在一台机器上访问失败率很高,监控显示如下图:
登录问题机器,发现该服务占用的内存高达30G,这是不正常的,疑似内存泄露。为了减少服务运行损失,先把该机器摘掉,再进行问题分析。
分析从现象上来看,服务的失败率,主要原因是内存泄露,导致内存占用高,从而影响到服务的正常运行。内存泄露大体可以分为两种:
1、申请了内存,没有释放,代码实现上的bug。
2、全局(或常驻内存)变量,因为某种原因申请了大量的内存,由于其生命周期未结束,暂时还没有释放。这可能是一种设计上的bug。
于是,查看了线上其他机器,并没有发现内存占用过高的情况,同时,也没有发现内存持续增长。从这个方面来看,更倾向于第二种情况,或者是机器自身问题。查看了问题机器上,该服务的运行日志及系统日志,并没有发现明显的问题。
因此,需要从服务进程去定位问题,分析步骤:
dump服务为了不影响线上业务,需要快速恢复服务。于是,先使用gcore命令将运行的进程dump下来,保存现场,命令如下:
# gcore 24564(进程pid)执行命令后,会在当前目录下生成一个该进程的core dump文件了。接着,重启服务后,观察了一段时间,并没有发现内存持续增长。
查看内存映射使用gdb调试刚才dump下来的core dump文件,查看进程中内存分配情况,如下:
(gdb) maintenance info sections………… 0x7f8c18000000->0x7f8c1c000000 at 0x3af828854: load398 ALLOC LOAD HAS_CONTENTS0x7f8c1c000000->0x7f8c1ffff000 at 0x3b3828854: load399 ALLOC LOAD HAS_CONTENTS0x7f8c1ffff000->0x7f8c20000000 at 0x3b7827854: load400 ALLOC LOAD READONLY HAS_CONTENTS0x7f8c20000000->0x7f8c23fe0000 at 0x3b7828854: load401 ALLOC LOAD HAS_CONTENTS0x7f8c23fe0000->0x7f8c24000000 at 0x3bb808854: load402 ALLOC LOAD READONLY HAS_CONTENTS0x7f8c28000000->0x7f8c2bffa000 at 0x3bb828854: load403 ALLOC LOAD HAS_CONTENTS0x7f8c2bffa000->0x7f8c2c000000 at 0x3bf822854: load404 ALLOC LOAD READONLY HAS_CONTENTS0x7f8c30000000->0x7f8c34000000 at 0x3bf828854: load405 ALLOC LOAD HAS_CONTENTS0x7f8c38000000->0x7f8c3c000000 at 0x3c3828854: load406 ALLOC LOAD HAS_CONTENTS0x7f8c40000000->0x7f8c43ffe000 at 0x3c7828854: load407 ALLOC LOAD HAS_CONTENTS0x7f8c43ffe000->0x7f8c44000000 at 0x3cb826854: load408 ALLOC LOAD READONLY HAS_CONTENTS………………可以看到分配了大量的内存,猜测这些内存块就是泄露的。为了定位到内存泄露的代码,尝试查看了这些几个内存块中的内容,看看是否能看到一些字符串或者有规律的内容,但是,并没有发现有价值的线索。
排查全局变量在gdb中,使用info variables命令,可以将进程中的全局变量,打印出来。但是由于引用了很多的第三方库等,可能输出内容会比较多。因此,可以考虑通过脚本进行过滤,重点排查该服务相关的变量。这里并不是说,第三方库不会有问题,但是,目前嫌疑最大的仍然是我们写的代码。针对筛选出来的全局变量,进行排查。重点检查的内容有,数组、vector、map等各种容器大小。
在排查中,发现其中一个全局变量中queue对象的_M_map_size = 10485758,这是不正常的。
(gdb) p Singleton::instance_->query_queue_$2 = {queue_ = {c = { = {_M_impl = { = { = {}, },members of std::_Deque_base::_Deque_impl:_M_map = 0x7f8ac2fff010,_M_map_size = 10485758,_M_start = {_M_cur = 0x7f8c08040f68,_M_first = 0x7f8c08040d70,_M_last = 0x7f8c08040f70,_M_node = 0x7f8ac5c17e80},_M_finish = {_M_cur = 0x7f903532af30,_M_first = 0x7f903532ade0,_M_last = 0x7f903532afe0,_M_node = 0x7f8ac7822960}}}, }},}可以看到,这是一个使用std::queue实现的队列,queue的内存结构由一个中控器(map)、多个缓冲区和两个迭代器(start和finish)组成。其中,中控器是一个连续的内存块,每个元素指向一个缓冲区。start和finish迭代器分别指向队列中头和尾。缓冲区中,存放队列中元素地址。如下:
根据调试信息,可以计算出:
map中node个数为:(0x7f8ac7822960-0x7f8ac5c17e80)/8=3675484个。
每个node对应一个缓冲区,缓冲区的大小为0x7f8c08040f70 - 0x7f8c08040d70 = 512字节。
由于地址占8字节,因此一个缓冲区中有:512/8=64个元素。
当前队列中,元素总数为:3675484*64=235230976。
通过查看内存中字符串占的内存,可以发现,缓存的字符串占用内存平均在100字符左右,计算这部分内存为:(235230976*100)/(1024*1024*1024.0)=21.9G。
与泄露的内存大小基本符合。基本可以认为,是由于队列中缓存元素过多,导致了服务的内存泄露。
阅读源码通过阅读源码,可以看到该变量的所在模块的功能是:实现一个带过期时间的缓存。具体实现逻辑是:每次请求从该缓存模块中取得一个key的缓存值,若该key已经在缓存中,且未过期,则直接返回对应的缓存值。若没有在缓存中(或已经过期),该将该key写入到一个队列中,由另一个更新线程去后台查询更新该key对应的缓存。
造成队列中数据大量堆积的可能原因是,某一时刻,更新线程查询后台时,出现了延时,引发堆积。由于访问量很大,且过期时间设定得较短,再加上,队列中重复key也并不会去重。因此,只要发生抖动,就会发生大量堆积,最终导致内存激增,服务异常。
为了验证问题,重新查看运行日志,确实能找到查询后台服务失败的相关日志,只是报错日志量不大,在最开始查看日志时,并未引起注意。至此,基本可以确认该问题。
总结对于线上问题处理来说,一般的原则是,先快速恢复服务,后定位问题。比如,因为新功能上线,导致服务异常,首先做的是代码回滚。本文中通过gcore将进程的内存dump下来,能较好地保存现场,同时,重启进程,恢复服务。因为已经保存了进程当时的内存状态,可以给分析问题提供较大地便利和更多地依据。
内存泄露的排查方法和工具有很多,如何在不重启服务、不重新编译、最小性能影响等方式下,快速定位到进程内存泄露点,仍有一定的挑战。
十年前512M内存的电脑用得飞快,而现在4G的还不够用,是因为软件太大还是程序优化的程度比以前降低了,抑或是硬件的问题? 我们一起思考下!先看几张图这是10年前的WindowsWindows Vist...
2024.11.14电脑常常遇到“内存不足”?加了内存条之后依然频繁提醒,这究竟是怎么回事?今日,英特尔官方在社交平台为粉丝解答了该问题。英特尔表示“很可能某个程序的代码错误导致电脑内存溢出,之前被占用的内存无法释放。可...
2024.11.18关于电脑的虚拟内存,是个老生常谈的问题了。说实话,对于如今电脑的硬件水平来说,虚拟内存设置能起到的作用并不大。不过,考虑到很多朋友可能仍有在用低配置老电脑的,所以就补充下这块的内容。所谓虚拟内存,是计...
2024.11.21正值双·11大促,惠普战66五代超值好价热卖中,锐龙版3999元起步,酷睿版4299元起步,大内存版4799元起,同时参加晒单评论活动的朋友,还可获赠原装大礼包(含背包,鼠标,和笔记本支架),性价比绝...
2024.11.22微信真的太吃内存了,夸张到什么程度呢?我打开手机的存储空间看了一眼,其他APP一年下来包缓存数据也就1个G左右,占用内存多一些的也就3个G,大型的游戏也不过12个G左右,而微信竟占了22个G!而且随着...
2024.11.22