前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >系统设计--内存泄漏该怎么分析?

系统设计--内存泄漏该怎么分析?

作者头像
程序员小王
发布2022-01-11 09:06:02
9450
发布2022-01-11 09:06:02
举报
文章被收录于专栏:架构说

历史文章

  • 吃土记之GDB调试原理 https://mp.weixin.qq.com/s/G8Rj0C5KuCpeCsWDxcY5Rg
  • 一个很有趣的fork面试程序,和大家分享下经验 https://mp.weixin.qq.com/s/hSzdMorkWqS2oqgAP6SXGA
  • 关于linux进程间的close-on-exec机制 https://mp.weixin.qq.com/s/NR9qJ7d5aLv7IZ-9iwZJUA
  • Nginx架构赏析 https://www.toutiao.com/i6995833333821227559/

目标

  1. 知道proc目录组成 2 知道proc的原理 (从内核代码角度详解proc目录)
  2. proc使用场景-/proc文件系统初探Linux内核态世界 (初探Linux内核态——通过proc文件系统作快速问题定位)
  3. 为什么有了top,gdb,strace valgrind 第三方工具 还要使用/proc目录
  4. shell检测内存泄漏脚本 (进程的smaps内存使用分析)

使用下面工具,会影响生产服务上性能吗?

敲黑板:不能为了解决一个问题,引发其他更多问题

  1. strace - trace system calls and signals

strace会追踪程序运行时的整个生命周期, 输出每一个系统调用的名字、参数、返回值和执行所消耗的时间等,是高级运维和开发人员排查问题的杀手铜。https://www.cnblogs.com/fadewalk/p/10847068.html

  • 只跟踪与文件操作有关的系统调用

strace -tt -f -e trace=file -p 8691 strace -f -p 4730 -T -tt -o /home/futi/strace_4730.log

strace -tt -f -e trace=network -p 8691

Process 8691 attached with 2 threads [pid 8691] 14:31:59.077383 accept(6, 0, NULL) = -1 EAGAIN (Resource temporarily unavailable)

代码语言:javascript
复制
-f          Trace child processes as they are created by currently traced processes as a result of the fork(2), vfork(2) and clone(2) system calls.

而-e trace!=open表示跟踪除了open以外的其他调用
        常见选项:
        -e trace=[set]    只跟踪指定的系统调用
        -e trace=file     只跟踪与文件操作有关的系统调用
        -e trace=process  只跟踪与进程控制有关的系统调用
        -e trace =network  只跟踪与网络有关的系统调用
        -e trace=signal   只跟踪与系统信号有关的系统调用
        -e trace=desc     只跟踪与文件描述符有关的系统调用
        -e trace=ipc      只跟踪与进程通信有关的系统调用
        -e abbrev=[set]   设定strace输出的系统调用的结果集
        -e raw=[set]      将指定的系统调用的参数以十六进制显示
        -e signal=[set]   指定跟踪的系统信号
        -e read=[set]     输出从指定文件中读出的数据
        -e write=[set]    输出写入到指定文件中的数据
         -e trace=network
                   Trace all the network related system calls.

       -e trace=signal
                   Trace all signal related system calls.

       -e trace=ipc
                   Trace all IPC related system calls.

       -e trace=desc
                   Trace all file descriptor related system calls.

       -e trace=memory
                   Trace all memory mapping related system calls.



  • BUGS A traced process runs slowly. 【why】
  • strace 它的实现基础是ptrace系统调用 ptrace - process trace

ptrace系统调用提供了一种方法来跟踪和控制进程的执行, 它可以读取和修改进程地址空间中的内容,包括寄存器的值。ptrace主要用于实现断点调试和跟踪系统调用

long ptrace(enum __ptrace_request request, pid_t pid, void *addr,void *data);

ptrace的四个参数的含义为:

  1. request:用于选择一个操作,见下文。
  2. pid:目标进程即被跟踪进程的pid。
  3. addr和data用于修改和拷贝被跟踪进程的进程地址空间的数据。
  • 原理

https://blog.csdn.net/jasonchen_gbd/article/details/44044539

https://fillzero.github.io/tool/process-of-strace.html

  1. 吃土记之GDB调试原理 https://mp.weixin.qq.com/s/G8Rj0C5KuCpeCsWDxcY5Rg
  2. strace 跟踪进程的系统调用的原理是什么?https://www.zhihu.com/question/263682345

how-does-strace-work 【ok】 https://blog.packagecloud.io/eng/2016/02/29/how-does-strace-work/

This blog post explains how strace works, internally. We’ll examine the ptrace system call, which strace relies on

This blog post explains how strace works, internally. We’ll examine the ptrace system call, which strace relies on

https://github.com/torvalds/linux/blob/v3.13/kernel/ptrace.c#L982-L984

How does ptrace work in Linux?【ok】

https://www.cnblogs.com/axiong/p/6184638.html

Why do shells call fork()?

  1. pstack是一个脚本工具,其核心实现就是使用了gdb以及thread apply all bt命令

gstack - print a stack trace of a running process

3 gdb 设置断点后,执行continue 降低 程序速度吗?

会的。

A traced process runs slowly.

  1. 使用linux的lsof和pmap解决fd和内存泄漏

关于C++的new是否会对内存初始化的问题

由此可见,C++在new时的初始化的规律可能为:对

  • 于有构造函数的类,不论有没有括号,都用构造函数进行初始化;
  • 如果没有构造函数,则不加括号的new只分配内存空间,不进行内存的初始化,
  • 而加了括号的new会在分配内存的同时初始化为0。

问题还原:new 返回值的地址 没有没有初始化

代码语言:javascript
复制
#include <iostream>

using namespace std;

class A
{
public:
    int a;
    A() : a(10){};
};

class B
{
public:
    int a;
};
void test1()
{
    A *pa = new A;
    cout << pa->a << endl;

    B *pb = new B();
    cout << pb->a << endl;
}

void test2()
{
    int *a = new int[1000];
    for (int i = 0; i < 10000; i++)
    {
        a[i] = i + 1;
    }
    delete[] a;

    int *b = new int[1000];
    for (int i = 0; i < 10; i++)
    {
        cout << b[i] << endl;
    }
}

void test3()
{
    int *a = new int[1000];
    for (int i = 0; i < 10; i++)
    {
        a[i] = i + 1;
    }

    delete[] a;
    int *b = new int[1000]();
    for (int i = 0; i < 10; i++)
    {
        cout << b[i] << endl;
    }
}
int main()
{

   // test1();
    test2();
    test3();
     return 0;
}


new int[1000] 没有初岁化,没有分配空间

  • 构造函数 顺序

C++多重继承的构造执行顺序:

1.首先执行虚基类的构造函数,多个虚基类的构造函数按照被继承的顺序构造;2.执行基类的构造函数,多个基类的构造函数按照被继承的顺序构造;

3.执行成员对象的构造函数,多个成员对象的构造函数按照声明的顺序构造;

4.执行派生类自己的构造函数;

5.析构以与构造相反的顺序执行;

问题还原:初始化和没有初始化区别?

代码语言:javascript
复制
#include <cstring>
#include <iostream>

int main()
{
      #define PAUSE(msg) std::cout << msg << std::endl; std::cin >> p

        char p;

        size_t size = 1024 * 1024 *100;
        char *l = new char[size]; //分配:0 物理内存
        char *l = new char[size](); //分配:100 物理内存
        PAUSE("new ");

        memset(l, 1, size / 2);
        PAUSE("using half large"); //分配50M 物理内存

        memset(l, 1, size); 
        PAUSE("using whole large"); //分配100M 物理内存

        delete []l;
        PAUSE("del");

        return 0;
}

The malloc() function allocates size bytes and returns a pointer to the allocated memory. // The memory is not initialized.

By default, Linux follows an optimistic memory allocation strategy. This means that when malloc() returns non-NULL there is no guarantee that the memory really is available. In case it turns out that the system is out of memory, one or more processes will be killed by the OOM killer. For more information, see the description of /proc/sys/vm/overcommit_memory and /proc/sys/vm/oom_adj in proc(5), and the Linux kernel source file Documentation/vm/overcommit-accounting.rst. https://www.acm-sigsim-mskr.org/Courseware/Fujimoto/Slides/FujimotoSlides-15-MemoryManagement.pdf

用过c做项目的话,对malloc 应该会印象至深。特别是分配出来的内存,没做初始化,里面是未定义的数据,这种在 c 里面经常出bug。

举个例子,malloc 分配出来一个结构体,里面有个字段表示状态。这种程序猿直接拿来用就很坑,所以很多时候用calloc分配,或malloc分配出来然后memset 0

我在c++经典的单机模式里面看到过,new应该是两个步骤,分配内存空间和对象初始化。这两个步骤还不是原子操作。应该就是对应着分配内存和写入操作

Linux fd 系列 — socket fd 是什么?

https://mp.weixin.qq.com/s/Ytw_N5zeLH50ItdgAsv3nA

  • tcp/ip 是网络协议栈,socket 是操作系统为了方便网络编程而设计出来的编程接口而已。
  • sockfs ,这也是个文件系统,只不过普通用户看不见,这是只由内核管理的文件系统
  • 只有socket文件描述符占用内存,一个socket大约3k。
  • https://cloud.tencent.com/developer/news/300336
  • https://www.cnblogs.com/xiaolincoding/p/12995358.html

ss -lnt

tcp_max_syn_backlog 128 增大半连接队列;开启 tcp_syncookies 功能 减少 SYN+ACK 重传次数

  1. 如果 SYN 半连接队列已满,只能丢弃连接吗?

开启 syncookies 功能就可以在不使用 SYN 半连接队列的情况下成功建立连接,在前面我们源码分析也可以看到这点,当开启了 syncookies 功能就不会丢弃连接。

syncookies 是这么做的:服务器根据当前状态计算出一个值,放在己方发出的 SYN+ACK 报文中发出,当客户端返回 ACK 报文时,取出该值验证,如果合法,就认为连接建立成功

2tcp_abort_on_overflow 共有两个值分别是 0 和 1,其分别表示:

0 :如果全连接队列满了,那么 server 扔掉 client 发过来的 ack ;1 :如果全连接队列满了,server 发送一个 reset 包给 client,表示废掉这个握手过程和这个连接;

  1. 如何知道应用程序的 TCP 全连接队列大小?
  • ss -lnt
  1. 如何模拟 TCP 全连接队列溢出的场景?

tcp_abort_on_overflow 共有两个值分别是 0 和 1,其分别表示:

0 :如果全连接队列满了,那么 server 扔掉 client 发过来的 ack ;1 :如果全连接队列满了,server 发送一个 reset 包给 client,表示废掉这个握手过程和这个连接;

如果要想知道客户端连接不上服务端,是不是服务端 TCP 全连接队列满的原因,那么可以把 tcp_abort_on_overflow 设置为 1, 这时如果在客户端异常中可以看到很多 connection reset by peer 的错误,

那么就可以证明是由于服务端 TCP 全连接队列溢出的问题

如果要想知道客户端连接不上服务端,是不是服务端 TCP 全连接队列满的原因,那么可以把 tcp_abort_on_overflow 设置为 1,这时如果在客户端异常中可以看到很多 connection reset by peer 的错误,那么就可以证明是由于服务端 TCP 全连接队列溢出的问题。

通常情况下,应当把 tcp_abort_on_overflow 设置为 0,因为这样更有利于应对突发流量。

举个例子,当 TCP 全连接队列满导致服务器丢掉了 ACK,与此同时,客户端的连接状态却是 ESTABLISHED,进程就在建立好的连接上发送请求。只要服务器没有为请求回复 ACK,请求就会被多次重发。如果服务器上的进程只是短暂的繁忙造成 accept 队列满,那么当 TCP 全连接队列有空位时,再次接收到的请求报文由于含有 ACK,仍然会触发服务器端成功建立连接。

所以,tcp_abort_on_overflow 设为 0 可以提高连接建立的成功率,只有你非常肯定 TCP 全连接队列会长期溢出时,才能设置为 1 以尽快通知客户端。

CP 全连接队列最大值从 128 增大到 5000 后,服务端抗住了 3 万连接并发请求,也没有发生全连接队列溢出的现象了。

  1. 四次挥手优化:

存储基础 — 文件描述符 fd 究竟是什么?

完整的架构图:

代码语言:javascript
复制
[root@realhost /]# cat /proc/meminfo
MemTotal:         688576 kB     总内存
MemFree:          153736 kB     空闲内存
MemAvailable:     339884 kB     可用内存
Buffers:              16 kB     给文件的缓冲大小
Cached:           267672 kB     高速缓冲存储器
SwapCached:           36 kB     被高速缓冲存储用的交换空间的大小
Active:           222900 kB     活跃使用中的高速缓冲存储器页面文件大小
Inactive:         123700 kB     不经常使用中的告诉缓冲存储器文件大小
Active(anon):      31800 kB     活跃的匿名内存(进程中堆上分配的内存,是用malloc分配的内存)
Inactive(anon):    57272 kB     不活跃的匿名内存
Active(file):     191100 kB     活跃的file内存,//file内存:磁盘高速缓存的内存空间和“文件映射(将物理磁盘上的文件内容与用户进程的逻辑地址直接关联)”的内存空间,其中的内容与物理磁盘上的文件相对应
Inactive(file):    66428 kB    不活跃的file内存     
Unevictable:           0 kB    不能被释放的内存页
Mlocked:               0 kB    mlock()系统调用锁定的内存大小
SwapTotal:       2097148 kB    交换空间总大小
SwapFree:        2096884 kB    空闲交换空间
Dirty:                 0 kB    等待被写回到磁盘的大小
Writeback:             0 kB    正在被写回的大小
AnonPages:         78876 kB       未映射页的大小
Mapped:            28556 kB    设备和文件映射大小
Shmem:             10160 kB    已经被分配的共享内存大小
Slab:             102916 kB    内核数据结构缓存大小
SReclaimable:      49616 kB    可收回slab的大小
SUnreclaim:        53300 kB    不可回收的slab的大小
KernelStack:        4416 kB      kernel消耗的内存
PageTables:         6028 kB      管理内存分页的索引表的大小
NFS_Unstable:          0 kB      不稳定页表的大小
Bounce:                0 kB      在低端内存中分配一个临时buffer作为跳转,把位于高端内存的缓存数据复制到此处消耗的内存
WritebackTmp:          0 kB      USE用于临时写回缓冲区的内存
CommitLimit:     2441436 kB      系统实际可分配内存总量
Committed_AS:     308028 kB      当前已分配的内存总量

VmallocTotal:   34359738367 kB   虚拟内存大小
VmallocUsed:      179588 kB    已经被使用的虚拟内存大小
VmallocChunk:   34359310332 kB   malloc 可分配的最大的逻辑连续的内存大小
HardwareCorrupted:     0 kB      删除掉的内存页的总大小(当系统检测到内存的硬件故障时)
AnonHugePages:      6144 kB      匿名 HugePages 数量
CmaTotal:              0 kB    总的连续可用内存
CmaFree:               0 kB      空闲的连续内存
HugePages_Total:       0      预留HugePages的总个数 
HugePages_Free:        0     池中尚未分配的 HugePages 数量
HugePages_Rsvd:        0        表示池中已经被应用程序分配但尚未使用的 HugePages 数量
HugePages_Surp:        0      这个值得意思是当开始配置了20个大页,现在修改配置为16,那么这个参数就会显示为4,一般不修改配置,这个值都是0
Hugepagesize:       2048 kB     每个大页的大小
DirectMap4k:      108416 kB   映射TLB为4kB的内存数量
DirectMap2M:      940032 kB   映射TLB为2M的内存数量
DirectMap1G:           0 kB     映射TLB为1G的内存数量

案例

https://www.cnblogs.com/arnoldlu/p/12063591.html

移动测试开发 Linux 下的 Nginx 内存泄露定位

https://testerhome.com/articles/20547

记一次堆外内存泄漏排查过程

https://segmentfault.com/a/1190000024435739 https://www.cnblogs.com/csnd/p/11807654.html

  • cat /proc//smaps > smaps.txt
  • dump指定内存地址到指定的目录下,参数的地址需要在smaps拿到地址前加上0x

dump memory /tmp/0x7fb9b0000000-0x7fb9b3ffe000.dump 0x7fb9b0000000 0x7fb9b3ffe000

代码语言:javascript
复制
显示长度超过10字符的字符串
  • strings -10 /tmp/0x7fb9b0000000-0x7fb9b3ffe000.dump PostgreSQL使用的内存去向
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Offer多多 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 历史文章
  • 目标
  • 使用下面工具,会影响生产服务上性能吗?
  • 关于C++的new是否会对内存初始化的问题
    • 问题还原:new 返回值的地址 没有没有初始化
      • 问题还原:初始化和没有初始化区别?
      • Linux fd 系列 — socket fd 是什么?
      • 存储基础 — 文件描述符 fd 究竟是什么?
      • 案例
        • 移动测试开发 Linux 下的 Nginx 内存泄露定位
          • 记一次堆外内存泄漏排查过程
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档