我们先来设定一下数据库,建立一个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. JQuery前端技术记录

    [Jquery-leearning notes-2015]by lijun 1   Jquery是javascript实现的库,目标在于改变web应用的高交互性的方式. 其不唐突性:样式(.css). ...

  2. UESTC 574 High-level ancients

    分析: 无论父节点增加了多少,子节点的增量总比父节点多1. 这种差分的关系是保存不变的,我们可以一遍dfs根据结点深度得到在根结点的每个点的系数. 估且把一开始的结点深度称做c0吧,对于子树的修改就只 ...

  3. POJ 2942 圆桌骑士

    之前做过这个题目,现在回想起来,又有新的柑橘. 求必须出去的骑士人数. 每一个双连通分量,如果是一个奇圈,那么一定是二分图染色失败. 依次遍历每个双连通分量,但是,对于邻接表中,有一些点不是双连通分量 ...

  4. Git使用02--branch分支, tag版本, 忽略文件 .gitingore

    一.分支 # 查看分支 git branch # 创建分支 git branch 分支名 # 切换分支 git checkout name # 创建并切换分支 git checkout -b name ...

  5. NDK下载地址

    官方下载地址 http://developer.android.com/ndk/downloads/index.html 没有提供旧版下载链接,只能修改链接方式下载 http://dl.google. ...

  6. java容易混淆的的内部类相关概念

    关于内部类: 作用: 1. 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类 2. 内部类的方法可以直接访问外部类的所有数据,包括私有的数据 3. 内部类所实现的 ...

  7. 9.异常Exception

    9.1 异常概述 package exception; /* * 异常:程序运行的不正常情况 * * Throwable: 异常的超类 * |-Error * 严重问题,这种问题我们通过异常处理是不能 ...

  8. Hibernate知识点小结(二)

    一.持久化对象和标识符    1.持久化类        配置完关系后,操作的实体对应的类,成为持久化类 (Customer) 2.持久化类标识符(oid:object id)        3.持久 ...

  9. Spring的声明式事务----Annotation注解方式(2)

    使用步骤: 步骤一.在spring配置文件中引入<tx:>命名空间<beans xmlns="http://www.springframework.org/schema/b ...

  10. cent os 上安装 matlab

    下载和安装可以参考,这个链接: https://lanseyujie.com/post/matlab-download-and-activate.html 上面这链接,在创建桌面快捷键时,未能创建,c ...