百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

如何定位内存泄漏(内存定位工具)

nanshan 2024-10-05 18:33 29 浏览 0 评论

介绍

本文主要介绍一种通过windbg分析内存泄漏的方法,方法也适用linux。

这个内存泄漏问题比较经典,我个人认为是自己这么多年bug定位中一个非常好的bug,并且在分析的过程中,也有许多需要思考的地方。通过该问题的分析,你可以了解到分析内存的基本方法和思路。

现象

后台检测程序在某天上午上报了内存警告,大概就是某程序的提交内存达到了1.0G。

这里需要解释下:在windows下32位应用程序如果提交内存大于某个阈值,比如我正常程序运行时提交内存最多应该只有500M,当检测程序发现该程序提交内存突然大于1.0G了,说明程序可能出现了内存泄漏。----当时就是这个进程的提交内存大于1.0G并发生了告警。

登陆后台查看,了解到如下信息:

  • 该进程已经连续运行了90天
  • 提交内存每天都在持续上涨,从启动到目前为止大概累计上升了800M。
  • 句柄、线程数等资源均正常

基本上可以确定程序存在内存泄漏,让运维通过工具保存了fulldump,并重启进程(否则内存告警会一直提示)。

这时候对于有经验的人员,这个问题因为并不没有对生产环境造成影响,且等到问题发生异常时还有比较长时间,所以可以不需要立刻恢复现场,否则当问题无法定位时而现场被破坏,将很难解决问题。

分析思路

  • 代码review:通过比较上个版本和上上个版本之间的差异,找到内存泄漏的地方。

的确是可以,但存在几个问题,因为本身每天内存泄漏的非常少,且之前版本大都一个月不到就升级了,不能确定这个问题是否是之前一个版本引入的,也可能是很多个版本前引入的?

其次:这个进程处理的消息类型很多,可能有问题的消息处理早就存在,只是最近一段时间其他服务升级,导致有bug的消息处理模块被触发。所以以上原因通过review近几个版本并不一定能找到。

还有,review可能能找到多个泄漏点,但可能存在遗漏的情况,并不是该问题的本质原因,修改后问题还可能存在。

但这个方法对于有人力富于的公司还是可以的,就让一个同事review代码,还是有效果的。

  • 静态代码检测工具:

公司没有基础,临时部署时间来不及。

  • 构建复现环境:由于问题出现原因不知,而复现时间太长,找不到快速复现的方法。

在平时工作中,通过复现注释代码缩小可疑模块,是我们大都会用的有效方法,但这个场景很难找到复现方法。

  • 规避问题:通过每周半夜重启程序,规避该问题。

这个方法在很多公司都存在,因为疑难问题的解决的确非常耗时,所以一般会有一个看门狗程序,在客户不知不觉时重启进程,快速恢复,也是非常常用的方法。这对于我来说,是下下策,不到万不得已,不会使用,印象中自己没怎么用过。

  • 通过技能查找问题的根本原因。

umdh:通过在A时间点获取一个进程内存镜像,然后一段时间出现内存泄漏后,在B时间点再获取一个进程内存镜像,通过比较找到之间的差异。理论可行,但对于这个问题意义不大,本身进程是一个高并发进程,每秒都要处理上百个消息,内存有上百次的申请和释放,A和B比较后差异会非常大,很难找到真实的内存泄漏模块。

通过以上思考,在有限人力下,通过windbg分析dump的内存,查找真实内存泄漏是快速并有效的方法,下面我就针对该问题给大家介绍下我的分析思路,最后问题的解决大致花费了半个工作日的时间。

准备工作

当时的dump我保存到了百度网盘。

  • [下载地址](https://pan.baidu.com/s/1vUjAr7edFTxxcKGnGEaatQ "下载地址")(提取码:11bg)
  • 设置好系统的pdb
e:\mylocalsymbols;SRV*e:\mylocalsymbols*http://msdl.microsoft.com/download/symbols

分析方法

C++的release版程序,内存携带的信息是非常有限的,大致就是三个维度:

  • 内存大小:每次malloc申请的大小,通过大小,我们可以找到对应的结构体、类
  • 内存地址内容:通过查看内存地址内容,比如有字符串、有特殊的值,找到申请的模块
  • 内存申请次数:通过每小时申请的频率,可以找到具体的消息类型

下面就是通过这三个维度找到具体的原因。

查找内存大小

打印所有堆块信息

!heap -s

显示如下

0:000> !heap -s
HEAPEXT: Unable to read ntdll!RtlpDisableHeapLookaside
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
-----------------------------------------------------------------------------
006f0000 00000002 1246976 1241928 1246976 982 236 81 0 a LFH
00190000 00001002 3136 1564 3136 390 7 3 0 0 LFH
External fragmentation 24 % (7 free blocks)
00110000 00001002 256 4 256 1 1 1 0 0
02050000 00001002 256 176 256 1 18 1 0 0 LFH
02240000 00001002 256 4 256 2 1 1 0 0
006a0000 00001002 64 12 64 4 2 1 0 0
044f0000 00001002 256 216 256 7 4 1 0 0 LFH
119d0000 00001002 7424 5820 7424 134 133 4 0 c8 LFH
14290000 00001003 256 4 256 2 1 1 0 bad
141d0000 00001003 256 4 256 2 1 1 0 bad
17f20000 00001003 256 4 256 2 1 1 0 bad
19030000 00001003 256 4 256 2 1 1 0 bad
191b0000 00001003 256 4 256 2 1 1 0 bad
19380000 00001003 256 4 256 2 1 1 0 bad
19300000 00001003 256 4 256 2 1 1 0 bad
155f0000 00001003 256 4 256 2 1 1 0 bad
-----------------------------------------------------------------------------

通过观察,我们知道了是006f0000堆块占用了大量内存

HEAPEXT: Unable to read ntdll!RtlpDisableHeapLookaside
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
-----------------------------------------------------------------------------
006f0000 00000002 1246976 1241928 1246976 982 236 81 0 a LFH

查看堆块内存百分比

内存持续上涨可能是某块固定大小内存被重复申请,所以统计下该堆块中各个内存大小的分配次数

!heap -stat -h 006f0000

查找堆中各个内存大小占用的百分比

0:000> !heap -stat -h 006f0000
unable to resolve ntdll!RtlpStackTraceDataBase
heap @ 006f0000
group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
14 23acbbe - 2c97ead8 (92.78)
a4 2ba0c - 1bf2fb0 (3.63)
1000 8f5 - 8f5000 (1.16)
1a4 3b9c - 61cbf0 (0.79)
20c 15fb - 2cfdc4 (0.37)
25 b77d - 1a8511 (0.22)
64 3ba0 - 174a80 (0.19)
24 75ae - 108c78 (0.13)
11c e4a - fda18 (0.13)
84c 164 - b89b0 (0.09)
400 172 - 5c800 (0.05)
234 265 - 54684 (0.04)
1c 2c2e - 4d508 (0.04)
1c0 287 - 46c40 (0.04)
c00 4b - 38400 (0.03)
20 1a12 - 34240 (0.03)
3bc ce - 30148 (0.02)
50 8da - 2c420 (0.02)
800 4c - 26000 (0.02)
2ba d2 - 23c94 (0.02)

size #blocks total ( %) (percent of total busy bytes)
14 23acbbe - 2c97ead8 (92.78)

TOP 20 中显示,最多的一个大小为 0x014 的分配次数为 0x23acbbe 次, 总共大概有700M左右。基本接近内存泄漏的总数。

所以这里得出几个结论:

  • 每次内存泄漏的大小是20字节。
  • 总共分配了0x23acbbe次,运行了90天,也就是每小时17318次/小时

定位内存来源

找到了大量的内存是0x014字节大小的,但是根据这个条件我们也找不到具体的代码啊?下面是几个思路

  • 根据大小

根据内存大小(0x14)去代码中查找大小为(0x14)的类、结构体、宏等等相关代码,然后找到原因。有几个问题:

1)、进程包含了很多其他组的dll,有的我没代码权限,无法遍历

2)、结构体、类太多了,人眼遍历太难了(针对这个问题我后来开发了一个工具,通过pdb文件可以找到程序中指定大小的所有结构体和类,后续章节讲解)

  • 内存内容

显示所有大小为(0x14)内存的地址,看它的地址内容有没有什么特点,比如是否有特殊的字符串、固定的二进制头??? 显示所有分配大小为 0x14的内存

命令
!heap -flt s 14
0:000> !heap -flt s 14
unable to resolve ntdll!RtlpStackTraceDataBase
_HEAP @ 6f0000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
0071c038 0004 0000 [00] 0071c040 00014 - (busy)
0071c2e8 0004 0004 [00] 0071c2f0 00014 - (busy)
0071e498 0004 0004 [00] 0071e4a0 00014 - (busy)
0071e4f8 0004 0004 [00] 0071e500 00014 - (busy)
0071e518 0004 0004 [00] 0071e520 00014 - (busy)
0071e5f8 0004 0004 [00] 0071e600 00014 - (busy)
0071e638 0004 0004 [00] 0071e640 00014 - (busy)
0071e658 0004 0004 [00] 0071e660 00014 - (busy)
0071e798 0004 0004 [00] 0071e7a0 00014 - (busy)
007374f0 0004 0004 [00] 007374f8 00014 - (busy)
00737510 0004 0004 [00] 00737518 00014 - (busy)
00737530 0004 0004 [00] 00737538 00014 - (busy)
00737550 0004 0004 [00] 00737558 00014 - (busy)
00737570 0004 0004 [00] 00737578 00014 - (busy)
00737590 0004 0004 [00] 00737598 00014 - (busy)
007375b0 0004 0004 [00] 007375b8 00014 - (busy)
007375d0 0004 0004 [00] 007375d8 00014 - (busy)
007375f0 0004 0004 [00] 007375f8 00014 - (busy)
00737610 0004 0004 [00] 00737618 00014 - (busy)
00737630 0004 0004 [00] 00737638 00014 - (busy)
00737650 0004 0004 [00] 00737658 00014 - (busy)
00737670 0004 0004 [00] 00737678 00014 - (busy)
00737690 0004 0004 [00] 00737698 00014 - (busy)
..............
..............

随机抽查几个地址,看下地址内存,都是00 00 00 00 00

大都是这样的值,实在是看不出规律。

建议

一般公司都会封装malloc、new函数,并分配一个模块号,每个内存地址头部都会携带id号,如下:

xxx_malloc(int nModleID,size_t size);

这样通过地址空间头也可以找到分配的模块。

  • 分配次数

大小0x14的内存在90天时间内总共分配了23acbbe 次, 0x23acbbe = 37407678/(90(天)*24(小时) ≈ 17318次/小时。 这个内存几乎每小时被申请17318次。进程有个统计功能:每个小时会统计处理的消息类型次数,那分析下数量级在1w~3w左右的消息即可,大概是4个消息类型,然后通过对这四个代码review才发现内存泄漏点。

if(total_fee){
LPADD_FEE pAddFee = new ADD_FEE;
ZeroMemory(pAddFee, sizeof(ADD_FEE));
pAddFee->nFee = total_fee;
gdt.nTotalFee = total_fee;
}

结构体 ADD_FEE ,刚好是20字节

typedef struct _tagADD_FEE{
int nFee;
int nReserved[4];
}ADD_FEE, *LPADD_FEE;

完全符合!! 问题解决

总结

这是一个低级错误导致的。为了避免类视问题,引入代码静态检测

1)、cppcheck

2)、pclint

最后选了pclint。配合jenkins,每天凌晨进行代码静态检查,并输出和上个版本的diff文件,下次就不会出现这么低级的问题。

在大公司里面都会有非常多的检测工具、流程、方法论,都是前人经验的积累,虽然有点冗余繁琐,但却非常有效。当你离开这个平台后,缺少了这些流程,一旦遇到疑难问题你才发现自己能用的手段真的很少。

相关推荐

0722-6.2.0-如何在RedHat7.2使用rpm安装CDH(无CM)

文档编写目的在前面的文档中,介绍了在有CM和无CM两种情况下使用rpm方式安装CDH5.10.0,本文档将介绍如何在无CM的情况下使用rpm方式安装CDH6.2.0,与之前安装C5进行对比。环境介绍:...

ARM64 平台基于 openEuler + iSula 环境部署 Kubernetes

为什么要在arm64平台上部署Kubernetes,而且还是鲲鹏920的架构。说来话长。。。此处省略5000字。介绍下系统信息;o架构:鲲鹏920(Kunpeng920)oOS:ope...

生产环境starrocks 3.1存算一体集群部署

集群规划FE:节点主要负责元数据管理、客户端连接管理、查询计划和查询调度。>3节点。BE:节点负责数据存储和SQL执行。>3节点。CN:无存储功能能的BE。环境准备CPU检查JDK...

在CentOS上添加swap虚拟内存并设置优先级

现如今很多云服务器都会自己配置好虚拟内存,当然也有很多没有配置虚拟内存的,虚拟内存可以让我们的低配服务器使用更多的内存,可以减少很多硬件成本,比如我们运行很多服务的时候,内存常常会满,当配置了虚拟内存...

国产深度(deepin)操作系统优化指南

1.升级内核随着deepin版本的更新,会自动升级系统内核,但是我们依旧可以通过命令行手动升级内核,以获取更好的性能和更多的硬件支持。具体操作:-添加PPAs使用以下命令添加PPAs:```...

postgresql-15.4 多节点主从(读写分离)

1、下载软件[root@TX-CN-PostgreSQL01-252software]#wgethttps://ftp.postgresql.org/pub/source/v15.4/postg...

Docker 容器 Java 服务内存与 GC 优化实施方案

一、设置Docker容器内存限制(生产环境建议)1.查看宿主机可用内存bashfree-h#示例输出(假设宿主机剩余16GB可用内存)#Mem:64G...

虚拟内存设置、解决linux内存不够问题

虚拟内存设置(解决linux内存不够情况)背景介绍  Memory指机器物理内存,读写速度低于CPU一个量级,但是高于磁盘不止一个量级。所以,程序和数据如果在内存的话,会有非常快的读写速度。但是,内存...

Elasticsearch性能调优(5):服务器配置选择

在选择elasticsearch服务器时,要尽可能地选择与当前业务量相匹配的服务器。如果服务器配置太低,则意味着需要更多的节点来满足需求,一个集群的节点太多时会增加集群管理的成本。如果服务器配置太高,...

Es如何落地

一、配置准备节点类型CPU内存硬盘网络机器数操作系统data节点16C64G2000G本地SSD所有es同一可用区3(ecs)Centos7master节点2C8G200G云SSD所有es同一可用区...

针对Linux内存管理知识学习总结

现在的服务器大部分都是运行在Linux上面的,所以,作为一个程序员有必要简单地了解一下系统是如何运行的。对于内存部分需要知道:地址映射内存管理的方式缺页异常先来看一些基本的知识,在进程看来,内存分为内...

MySQL进阶之性能优化

概述MySQL的性能优化,包括了服务器硬件优化、操作系统的优化、MySQL数据库配置优化、数据库表设计的优化、SQL语句优化等5个方面的优化。在进行优化之前,需要先掌握性能分析的思路和方法,找出问题,...

Linux Cgroups(Control Groups)原理

LinuxCgroups(ControlGroups)是内核提供的资源分配、限制和监控机制,通过层级化进程分组实现资源的精细化控制。以下从核心原理、操作示例和版本演进三方面详细分析:一、核心原理与...

linux 常用性能优化参数及理解

1.优化内核相关参数配置文件/etc/sysctl.conf配置方法直接将参数添加进文件每条一行.sysctl-a可以查看默认配置sysctl-p执行并检测是否有错误例如设置错了参数:[roo...

如何在 Linux 中使用 Sysctl 命令?

sysctl是一个用于配置和查询Linux内核参数的命令行工具。它通过与/proc/sys虚拟文件系统交互,允许用户在运行时动态修改内核参数。这些参数控制着系统的各种行为,包括网络设置、文件...

取消回复欢迎 发表评论: