Golang使用pprof检查内存泄漏的全过程

 更新时间:2024年02月04日 09:35:33   作者:hankeyyh  
pprof 是golang提供的一款分析工具,可以分析CPU,内存的使用情况,本篇文章关注它在分析内存泄漏方面的应用,本文给大家介绍了Golang使用pprof检查内存泄漏的全过程,文中通过代码给大家介绍的非常详细,需要的朋友可以参考下

前言

pprof 是golang提供的一款分析工具,可以分析CPU,内存的使用情况,本篇文章关注它在分析内存泄漏方面的应用。pprof 不能直观显示出某个函数发生了泄漏,它的用途是列出当前内存分配较大的位置,通过比较一段时间pprof的结果,可以发现内存变化。

测试代码

比如我们有下面的函数,在请求中对全局变量分配了一块内存,但没有回收:

package main

import (
    "fmt"
    "log"
    "net/http"
    _ "net/http/pprof" // 关键!!
    "runtime"
    "sync"
)

type UserData struct {
    Data []byte
}

type UserCache struct {
    mu    sync.Mutex
    Cache map[string]*UserData
}

func (uc *UserCache) clear() {
    uc.mu.Lock()
    defer uc.mu.Unlock()
    uc.Cache = make(map[string]*UserData)
}

func NewUserCache() *UserCache {
    return &UserCache{
        Cache: make(map[string]*UserData),
    }
}

var userCache = NewUserCache()

// 分配全局内存
func handleRequest(w http.ResponseWriter, r *http.Request) {
    userCache.mu.Lock()
    defer userCache.mu.Unlock()
    
    userData := &UserData{
        Data: make([]byte, 1000000),
    }

    userID := fmt.Sprintf("%d", len(userCache.Cache))
    // 赋值给全局变量,但没有回收
    userCache.Cache[userID] = userData
    log.Printf("Added data for user %s. Total users: %d\n", userID, len(userCache.Cache))
}

// 清空全局内存
func handleClear(w http.ResponseWriter, r *http.Request) {
    userCache.clear()
    runtime.GC()
}

func main() {
    http.HandleFunc("/leaky-endpoint", handleRequest)
    http.HandleFunc("/clear", handleClear)
    
    http.ListenAndServe(":8080", nil)
}

其中 import "net/http/pprof" 引入了pprof工具,启动服务后,内存分析结果保存在http://localhost:8080/debug/pprof/heap。

发送请求

我们往/leaky-endpoint发送一些请求,触发全局内存分配,可以使用 ab 命令:

ab -n 1000 -c 10 http://localhost:8080/leaky-endpoint

-n:请求数量,我们总共发送1000个请求

-c:并发数量,1000个请求通过10个并发线程发送

分析内存

查看内存分配

我们可以通过下面的命令查看分析结果:

pprof http://localhost:8080/debug/pprof/heap

// output: 
// Entering interactive mode (type "help" for commands, "o" for options)
// (pprof)

输入上述命令后,会进入命令行交互模式,我们可以输入一系列帮助命令查看内存分析结果:

  • top:输出内存分配最多的几个函数
(pprof) top
Showing nodes accounting for 487.41MB, 100% of 487.41MB total
      flat  flat%   sum%        cum   cum%
  487.41MB   100%   100%   487.41MB   100%  main.handleRequest
         0     0%   100%   487.41MB   100%  net/http.(*ServeMux).ServeHTTP
         0     0%   100%   487.41MB   100%  net/http.(*conn).serve
         0     0%   100%   487.41MB   100%  net/http.HandlerFunc.ServeHTTP
         0     0%   100%   487.41MB   100%  net/http.serverHandler.ServeHTTP

可以看到,handleRequest 分配了最多的内存。这几列的含义是:

  • flat:函数自身在采样期间直接消耗的内存,不包括它调用的其他函数的消耗;
  • flag%: flat 值占总消耗资源的百分比,这个百分比是基于整个程序在采样期间的资源消耗来计算的;
  • sum%: 累积百分比,表示从输出列表顶部开始到当前行为止的所有函数的 flat 百分比之和。这可以帮助你理解最顶部的函数累积起来消耗了多少资源;
  • cum: 函数自身flat,加上它调用的所有函数在采样期间消耗的内存;
  • cum%: cum 值占总消耗资源的百分比。这个百分比显示了函数及其所有递归调用的资源消耗在程序总资源消耗中的比重;
  • list:列出函数具体分配内存的位置
(pprof) list main.handleRequest
Total: 487.41MB
ROUTINE ======================== main.handleRequest in /home/yuhanyang/project/HelloGo/test_mem_leak/main.go
 487.41MB   487.41MB (flat, cum)   100% of Total
        .          .     35:func handleRequest(w http.ResponseWriter, r *http.Request) {
        .          .     36:   userCache.mu.Lock()
        .          .     37:   defer userCache.mu.Unlock()
        .          .     38:
        .          .     39:   userData := &UserData{
 487.41MB   487.41MB     40:           Data: make([]byte, 1000000),
        .          .     41:   }
  • web:在浏览器中生成并打开 SVG 格式的调用图

  • png:生成 PNG 格式的调用图

比较内存分配

为了分析内存泄漏,我们往往需要统计一段时间开始和结束时的内存变化情况,在上面的基础上,我们可以再用ab命令触发几次内存分配,然后通过pprof查看:

>>pprof  http://localhost:8080/debug/pprof/heap
(pprof) top
Showing nodes accounting for 3.74GB, 100% of 3.74GB total
      flat  flat%   sum%        cum   cum%
    3.74GB   100%   100%     3.74GB   100%  main.handleRequest
         0     0%   100%     3.74GB   100%  net/http.(*ServeMux).ServeHTTP
         0     0%   100%     3.74GB   100%  net/http.(*conn).serve
         0     0%   100%     3.74GB   100%  net/http.HandlerFunc.ServeHTTP
         0     0%   100%     3.74GB   100%  net/http.serverHandler.ServeHTTP

发现内存较之前增长了,实际上我们可以通过 -diff_base 来比较两次采样结果的差异:

// 第一次采样
pprof http://localhost:8080/debug/pprof/heap > base.out

// 当前采样
pprof http://localhost:8080/debug/pprof/heap > current.out

// 差异分析
pprof -diff_base=base.out current.out

查看启动以来的内存分配

上面的pprof实际统计的是采样时刻的内存分配情况,我们可以先清空全局内存,然后再通过top查看,会发现不再有输出了:

curl http://localhost:8080/clear
pprof  http://localhost:8080/debug/pprof/heap
(pprof) top
Showing nodes accounting for 0, 0% of 0 total
      flat  flat%   sum%        cum   cum%

但我们可以通过-sample_index=来控制采样,对于内存分析来说,它有下面几种取值:

  • inuse_space:默认,表示程序在执行时刻正在使用的内存量;
  • inuse_objects:程序在执行时刻正在使用的对象数量;
  • alloc_space:程序自启动以来分配的总内存量,不考虑这些内存是否已经被释放;
  • alloc_objects:程序自启动以来分配的总对象数量,不考虑这些对象是否已经被释放;

我们可以查看程序启动以来的总内存分配数据:

pprof -sample_index=alloc_space http://localhost:8080/debug/pprof/heap
(pprof) top
Showing nodes accounting for 3842.05MB, 99.44% of 3863.75MB total
Dropped 44 nodes (cum <= 19.32MB)
Showing top 10 nodes out of 14
      flat  flat%   sum%        cum   cum%
 3832.36MB 99.19% 99.19%  3832.86MB 99.20%  main.handleRequest
    9.70MB  0.25% 99.44%    19.36MB   0.5%  compress/flate.NewWriter

以上就是Golang使用pprof检查内存泄漏的全过程的详细内容,更多关于Golang pprof内存泄漏的资料请关注脚本之家其它相关文章!

相关文章

  • go singleflight缓存雪崩源码分析与应用

    go singleflight缓存雪崩源码分析与应用

    这篇文章主要为大家介绍了go singleflight缓存雪崩源码分析与应用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • Golang的os标准库中常用函数的整理介绍

    Golang的os标准库中常用函数的整理介绍

    这篇文章主要介绍了Go语言的os标准库中常用函数,主要用来实现与操作系统的交互功能,需要的朋友可以参考下
    2015-10-10
  • golang读取yaml文件的示例代码

    golang读取yaml文件的示例代码

    本文主要介绍了golang读取yaml文件的示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-09-09
  • Golang中int类型和字符串类型相互转换的实现方法

    Golang中int类型和字符串类型相互转换的实现方法

    在日常开发中,经常需要将数字转换为字符串或者将字符串转换为数字,在 Golang 中,有一些很简便的方法可以实现这个功能,接下来就详细讲解一下如何实现 int 类型和字符串类型之间的互相转换,需要的朋友可以参考下
    2023-09-09
  • Golang实现字符串倒序的几种解决方案

    Golang实现字符串倒序的几种解决方案

    给定一个字符串,按单词将该字符串逆序是我们大家在开发中可能会遇到的一个需求,所以下面这篇文章主要给大家介绍了关于Golang如何实现字符串倒序的几种解决方案,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-10-10
  • Golang捕获panic堆栈信息的讲解

    Golang捕获panic堆栈信息的讲解

    今天小编就为大家分享一篇关于Golang捕获panic堆栈信息的讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-04-04
  • 详解Go语言中切片的长度与容量的区别

    详解Go语言中切片的长度与容量的区别

    切片可以看成是数组的引用,切片的长度是它所包含的元素个数。切片的容量是从它的第一个元素到其底层数组元素末尾的个数。本文将通过示例详细讲讲Go语言中切片的长度与容量的区别,需要的可以参考一下
    2022-11-11
  • Go语言Gin框架获取请求参数的两种方式

    Go语言Gin框架获取请求参数的两种方式

    在添加路由处理函数之后,就可以在路由处理函数中编写业务处理代码了,而编写业务代码第一件事一般就是获取HTTP请求的参数吧,Gin框架在net/http包的基础上封装了获取参数的方式,本文小编给大家介绍了获取参数的两种方式,需要的朋友可以参考下
    2024-01-01
  • go chan基本使用详解

    go chan基本使用详解

    本文主要介绍了go chan基本使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • Golang轻量级IoC容器安装使用示例

    Golang轻量级IoC容器安装使用示例

    这篇文章主要为大家介绍了Golang轻量级IoC容器安装使用示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06

最新评论