我们先来设定一下数据库,建立一个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. python:包与异常处理

    一.包 1,什么是包? 把解决一类问题的模块放在同一个文件夹里-----包 2,包是一种通过使用‘.模块名’来组织python模块名称空间的方式. 1. 无论是import形式还是from...imp ...

  2. struts2 实现文件下载方法汇总

    http://pengranxiang.iteye.com/blog/259401 一.通过struts2提供的下载机制下载文件: 项目名为 struts2hello ,所使用的开发环境是MyEcli ...

  3. git快捷命令缩写

    # Query/use custom command for `git`. zstyle -s ":vcs_info:git:*:-all-" "command" ...

  4. $_GET 与 $POST

    $_GET就是地址传值,用 '?' 开始传值,多个值间用 '&' 号分隔,多用于简单的传值,比如说看新闻需要新闻id一般就会用地址传值, $_GET的好处是传值可见,也就是只要一个地址就ok了 ...

  5. mssql数据库迁移到mysql

    使用mysql migration toolkit工具来进行迁移.(需要安装jdk6 java的安装包) 发现数据量大的表却没能迁过来.软件使用比较容易,配置下源数据库信息,和目标数据库信息就可以进行 ...

  6. SpringBoot非官方教程 | 第二十一篇: springboot集成JMS

    转载请标明出处: http://blog.csdn.net/forezp/article/details/71024024 本文出自方志朋的博客 springboot对JMS提供了很好的支持,对其做了 ...

  7. 菜鸟崛起 DB Chapter 1 数据库概述

    1. 数据库的概述 在目前阶段,如果要存储和管理数据,则离不开数据库.当数据存储到数据库后,就会通过数据库管理系统对这些数据进行组织和管理.数据库由一批数据构成有序的集合,这些数据被存放在结构化的数据 ...

  8. deprecate (声明不赞成)

    deprecate (声明不赞成)  只是不赞成,不影响使用,或者你升级包 解决:update to 高版本 npm update [-g] [<pkg>...]

  9. 第13届景驰-埃森哲杯广东工业大学ACM程序设计大赛--F-等式

    链接:https://www.nowcoder.com/acm/contest/90/F 来源:牛客网 1.题目描述 给定n,求1/x + 1/y = 1/n (x<=y)的解数.(x.y.n均 ...

  10. 3930: [CQOI2015]选数

    Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 1958  Solved: 979[Submit][Status][Discuss] Descripti ...