我们先来设定一下数据库,建立一个MySQL数据库表,名为users,里面有login_name、nickname、uid、password、forbidden几个字段,其中uid与forbidden为int类型字段,其他均为varchar类型,而password为用户密码md5后的结果,因此长度均为32。我们使用的MySQL数据库引擎为go-sql-driver/mysql

create database mytest default character set utf8;
use mytest ; create table users(login_name varchar(20),nickname varchar(20),uid int(8),password char(32),forbidden tinyint(1)); insert into users value ('Alex','Sunday','12345678','827ccb0eea8a706c4c34a16891f84e7b','0');

  

package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"os"
"os/signal"
"runtime"
"runtime/pprof"
"strings"
"time"
) var (
pid int
progname string
) func init() {
pid = os.Getpid()
paths := strings.Split(os.Args[0], "/")
paths = strings.Split(paths[len(paths)-1], string(os.PathSeparator))
progname = paths[len(paths)-1] runtime.MemProfileRate = 1
} func saveHeapProfile() {
runtime.GC() f, err := os.Create(fmt.Sprintf("heap_%s_%d_%s.prof", progname, pid, time.Now().Format("2006_01_02_03_04_05")))
if err != nil {
return
}
defer f.Close()
pprof.Lookup("heap").WriteTo(f, 1)
} func waitForSignal() os.Signal {
signalChan := make(chan os.Signal, 1)
defer close(signalChan) signal.Notify(signalChan, os.Kill, os.Interrupt)
s := <-signalChan
signal.Stop(signalChan)
return s
} func connect(source string) *sql.DB {
db, err := sql.Open("mysql", source)
if err != nil {
return nil
} if err := db.Ping(); err != nil {
return nil
} return db
} type User struct {
uid int
name string
nick string
forbidden int
cid int
} func query(db *sql.DB, name string, id int, dataChan chan *User) {
for {
time.Sleep(time.Millisecond) user := &User{
cid: id,
name: name,
} err := db.QueryRow("SELECT nickname, uid, forbidden FROM users WHERE login_name = ?", name).Scan(&user.nick, &user.uid, &user.forbidden)
if err != nil {
continue
} dataChan <- user
}
} func main() {
defer saveHeapProfile() db := connect("mytest:mytest@tcp(localhost:3306)/mytest?charset=utf8")
if db == nil {
return
} userChan := make(chan *User, 100)
for i := 0; i < 100; i++ {
go query(db, "Alex", i+1, userChan)
} allUsers := make([]*User, 1<<12)
go func() {
for user := range userChan {
fmt.Printf("routine[%d] get user %+v\n", user.cid, user)
allUsers = append(allUsers, user)
}
}() s := waitForSignal() fmt.Printf("signal got: %v, all users: %d\n", s, len(allUsers))
}

  

上面的程序当然有蛮严重的内存泄漏问题,我们下面来看看如何加入代码,让pprof帮我们定位到产生内存泄漏的具体代码段里。

下面是内存泄漏问题诊断的一般流程:

  1. 在命令行下 go build  编译生成一个可执行程序,例如叫做your-executable-name, 然后运行让其跑起来(会一直不停的跑直到你主动中断,系统会监控到中断信号 并将heap信息写入生成的文件中 我们称其为profile-filename)
  2. 使用go tool pprof your-executable-name profile-filename即可进入pprof命令模式分析数据
  3. 或者使用go tool pprof your-executable-name --text profile-filename查看各个函数/方法的内存消耗排名
  4. 或者使用go tool pprof your-executable-name --dot profile-filename > heap.gv命令生成可以在graphviz里面看的gv文件,在查看各个方法/函数的内存消耗的同时查看它们之间的调用关系
  5. 或者生成了gv文件之后通过dot -Tpng heap.gv > heap.png生成调用关系网与内存消耗图的png图形文件

之后执行go tool pprof your-executable-name --text profile-filename即可得到类似下面的结果(仅截取前几行):


Adjusting heap profiles for 1-in-1 sampling rate
Total: 1.7 MB
0.7 40.4% 40.4% 1.0 56.2% github.com/go-sql-driver/mysql.(*MySQLDriver).Open
0.5 27.7% 68.1% 1.6 93.6% main.query
0.2 11.7% 79.8% 0.2 11.7% newdefer
0.1 6.9% 86.7% 0.1 6.9% database/sql.convertAssign
0.1 4.6% 91.3% 0.1 4.7% main.func路001
0.0 1.2% 92.5% 0.0 1.2% net.newFD
0.0 1.0% 93.5% 0.0 1.0% github.com/go-sql-driver/mysql.parseDSNParams
0.0 0.9% 94.5% 0.0 0.9% runtime.malg
0.0 0.6% 95.1% 0.0 0.6% runtime.allocm
0.0 0.5% 95.6% 0.0 0.5% resizefintab
0.0 0.5% 96.1% 0.0 0.5% github.com/go-sql-driver/mysql.(*mysqlConn).readColumns
0.0 0.5% 96.6% 0.0 0.5% database/sql.(*DB).addDepLocked

这个表格里每一列的意义参见perftool的这个文档

运行go tool pprof命令,不带–-text参数后将直接进入pprof的命令行模式,可以首先执行top10,就可以得到与上述结果类似的排名,

从里面可以看到消耗内存最多的是mysql的Open方法,说明我们调用了Open方法后没有释放资源。

此外我们也可以运行go tool pprof your-executable-name --dot profile-filename > heap.gv,这样将得到一个heap.gv文件,我们在graphviz里面打开这个文件将得到一个更详细的包括调用关系在内的内存消耗图。当然,我们如果只需要一张图,也可以运行dot -Tpng heap.gv > heap.png将这个gv文件另存为png图,这样就可以像我一样,在下面展示剖析结果了。

除了在给定的时刻打印出内存剖析信息到文件里以外,如果你希望能够随时看到剖析结果,也可以有很简单的方法,那就是把net/http和net/http/pprof这两个包给import进来,其中net/http/pprof包需要以import _ "net/http/pprof"的方式导入,然后在代码里面加一个自定义端口(如6789)的http服务器,像这样:

go func(){
http.ListenAndServe(":6789", nil)
}()

  

这样,在程序运行起来以后,你就可以通过go tool pprof your-executable-name http://localhost:6789/debug/pprof/heap获得实时的内存剖析信息了,数据格式与通过文件保存下来的格式一致,之后的处理就都一样了。

在go tool pprof之后,进入pprof的命令行模式下,可以使用list命令查看对应函数(实际上是匹配函数名的正则表达式)里具体哪一行导致的性能/内存损耗。

转自:http://blog.raphaelzhang.com/2014/01/memory-leak-detection-in-go/

在Go语言里检测内存泄漏的更多相关文章

  1. 使用Visual Leak Detector检测内存泄漏[转]

      1.初识Visual Leak Detector 灵活自由是C/C++语言的一大特色,而这也为C/C++程序员出了一个难题.当程序越来越复杂时,内存的管理也会变得越加复杂,稍有不慎就会出现内存问题 ...

  2. Android性能优化之利用LeakCanary检测内存泄漏及解决办法

    前言: 最近公司C轮融资成功了,移动团队准备扩大一下,需要招聘Android开发工程师,陆陆续续面试了几位Android应聘者,面试过程中聊到性能优化中如何避免内存泄漏问题时,很少有人全面的回答上来. ...

  3. VC使用CRT调试功能来检测内存泄漏

    信息来源:csdn     C/C++ 编程语言的最强大功能之一便是其动态分配和释放内存,但是中国有句古话:“最大的长处也可能成为最大的弱点”,那么 C/C++ 应用程序正好印证了这句话.在 C/C+ ...

  4. 如何在linux下检测内存泄漏

    之前的文章应用 Valgrind 发现 Linux 程序的内存问题中介绍了利用Linux系统工具valgrind检测内存泄露的简单用法,本文实现了一个检测内存泄露的工具,包括了原理说明以及实现细节. ...

  5. Vc 检测内存泄漏

    启用内存泄漏检测 检测内存泄漏是 C/c + + 调试器和 C 运行时库 (CRT) 的主要工具调试堆函数. 若要启用调试堆的所有函数,在 c + + 程序中,按以下顺序包含以下语句: C++复制 # ...

  6. 如何在linux下检测内存泄漏(转)

    本文转自:http://www.ibm.com/developerworks/cn/linux/l-mleak/ 本文针对 linux 下的 C++ 程序的内存泄漏的检测方法及其实现进行探讨.其中包括 ...

  7. monkey检测内存泄漏

    monkey中检查内存泄漏,实际上是对一个操作多次操作后看内存情况,内存泄漏具体的原理可百度,现在我们梳理检测内存泄漏的方法: 测试前你需要安装: 1.MAT分析工具 2.使用工具事实监控内存指标,现 ...

  8. Qt creator 搭配 valgrind 检测内存泄漏

    继上次重载operator new检测内存泄漏失败之后,妥协了.决定不管是否是准确指明哪一行代码出现内存泄漏,只要告诉我是否有泄漏就行了,这样就没有new替换的问题.在开发中,总是一个个小功能的开发. ...

  9. 重载new和delete来检测内存泄漏

    重载new和delete来检测内存泄漏 1. 简述 内存泄漏属于资源泄漏的一种,百度百科将内存泄漏分为四种:常发性内存泄漏.偶发性内存泄漏.一次性内存泄漏和隐式内存泄漏.    常发性指:内存泄漏的代 ...

随机推荐

  1. Spring3+Struts2+Hibernate4+Mybatis整合的一个maven例子

    说明: 1.用了maven去搞这个demo,懒得去导jar包... 2.这个demo用了spring去做Ioc,事务的aop:用了struts2去做“MVC”(没有用到任何UI技术,有点对不起这个MV ...

  2. Shell脚本学习之expect命令

     转载:http://blog.csdn.net/leexide/article/details/17485451 目录(?)[-] 一概述 二expect的安装 一Tcl 安装 二expect 安装 ...

  3. Gym - 101246D 博弈

    题意:一个无向有环的图,从 1 号结点起火,开始蔓延,两个绝顶聪明的人轮流走,谁不能走谁输,输出输的人: 分析: 当时知道是博弈,但是想当然的以为 1 号结点有一个奇数层,就必胜:其实不是这样的,当一 ...

  4. Uva 10217 概率

    题意: 假设一年有n天, 有一些人排队买票,会有一个人中奖,这个人是,他的生日和前面的某一个人相同: 求最佳整数位置,和最佳实数位置: 分析: 第一个人获奖的概率(他和售票员的生日相同): 1/N 第 ...

  5. 郑州集训day1自闭有感

    被拉到郑州培训了 考了一上午莫名自闭 帮助慎老师拿到\(rk1\)非常开心 简述一下题目吧 T1.まんふは函数 原题地址 考原题还行 据说是\(Huffman\)树 在成爷爷的再三讲解下,我终于明白了 ...

  6. 【[SDOI2009]晨跑】

    板子 题意就是每个点只能经过一次 所以非常显然拆点,除去\(1,n\)每个点\(i\)向\(i'\)连一条容量为\(1\)费用为\(0\)的边 剩下的边按照输入给出的建就好了 代码 #include& ...

  7. 【[SCOI2012]喵星球上的点名】

    好题啊 \(SA+ST\text{表}+\text{莫队}\) 我们先强行把所有的串连起来,串与串之间插入特殊字符,姓和名之间也插入特殊字符 之后跑一遍\(SA\),求出\(sa\)和\(het\) ...

  8. L1 loss L2 loss

    https://www.letslearnai.com/2018/03/10/what-are-l1-and-l2-loss-functions.html http://rishy.github.io ...

  9. 二.Mybatis 增删改查

    Student.java package com.pojo; import java.util.Date; public class Student { int stuid; String stuNa ...

  10. 【luogu P1307 数字反转】 题解

    题目链接:https://www.luogu.org/problemnew/show/P1307 刚入门的一道字符串模拟,分四种情况讨论来做比较好. #include<iostream> ...