我们先来设定一下数据库,建立一个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. Android(java)学习笔记36:Scanner类使用

    1. Scanner类使用 package cn.itcast_01; /* * Scanner:用于接收键盘录入数据. * * 前面的时候: * A:导包 * B:创建对象 * C:调用方法 * * ...

  2. [19/03/22-星期五] 异常(Exception)(二)_捕获异常

    一.概念 捕获异常是通过3个关键词来实现的:try-catch-finally.用try来执行一段程序,如果出现异常,系统抛出一个异常,可以通过它的类型来捕捉(catch)并处理它, 最后一步是通过f ...

  3. 行云管家V4.9正式发布:监控全面提升,首页、主机详情大幅优化,新增大量实用功能.md

    让大家久等啦!4.9版本中我们对监控模块进行了重构,在数据准确性与稳定性方面做了大幅提升.我们也对首页及主机详情页面做了大幅重构,以追求为您提供极致的用户体验.同时我们在新版本中增加了如:运维报表.用 ...

  4. refinedet网络结构

    refinedet只预测4个层,并且只有conv6_1.conv6_2,没有ssd中的conv7.8.9 refinedet的4个层都只有1个aspect ratio和1个min_size,所以每层每 ...

  5. 【洛谷P2831】[NOIP2016]愤怒的小鸟

    愤怒的小鸟 题目链接 本来是刷状压DP的,然而不会.. 搜索是比较好想的,直接dfs就行了 我们可以知道两只猪确定一条抛物线 依次处理每一只猪,有以下几种方法: 1.先看已经建立的抛物线是否能打到这只 ...

  6. TDD: 测试静态方法

    对于静态方法,mock框架大多不支持测试.所以需要修改代码,以便调用的静态方法可测试. 1  一个静态方法类: public class MyHelper{ public static string ...

  7. HTML中id和class选择器

    <1>.id和class的区别? id相当于人的身份证不可以重复 class相当于人的名称可以重复 一个HTML标签只能绑定一个id名称 一个HTML标签可以绑定多个class名称 < ...

  8. 获取APP地图权限

    获取APP地图权限 NSLocationWhenUseUsageDescription,在info里面设置为空

  9. CF605A Sorting Railway Cars(递推)

    题目描述 An infinitely long railway has a train consisting of n cars, numbered from 1 to n (the numbers ...

  10. LintCode 7.Serialize and Deserialize Binary Tree(含测试代码)

    题目描述 设计一个算法,并编写代码来序列化和反序列化二叉树.将树写入一个文件被称为“序列化”,读取文件后重建同样的二叉树被称为“反序列化”. 如何反序列化或序列化二叉树是没有限制的,你只需要确保可以将 ...