关于内存泄露:使用mtrace追踪JVM堆外内存泄露

原创:扣钉日记(微信公众号ID:codelogs),欢送分享,非公众号转载保留此申明。简介在上篇文章中,介绍了应用tcmalloc或jemalloc定位native内存泄露的办法,但应用这个办法相当于更换了原生内存分配器,以至于应用时会有一些顾虑。 通过一些摸索,发现glibc自带的ptmalloc2分配器,也提供有追踪内存泄露的机制,即mtrace,这使得产生内存泄露时,可间接定位,而不须要额定装置及重启操作。 mtrace追踪内存泄露glibc中提供了mtrace这个函数来开启追踪内存调配的性能,开启后每次应用程序调用malloc或free函数时,会将内存调配开释操作记录在MALLOC_TRACE环境变量所指的文件外面,如下: $ pid=`pgrep java`# 配置gdb不调试信号,防止JVM收到信号后被gdb暂停$ cat <<"EOF" > ~/.gdbinithandle all nostop noprint passhandle SIGINT stop print nopassEOF# 设置MALLOC_TRACE环境变量,将内存调配操作记录在malloc_trace.log里$ gdb -q -batch -ex 'call setenv("MALLOC_TRACE", "./malloc_trace.log", 1)' -p $pid# 调用mtrace开启内存调配追踪$ gdb -q -batch -ex 'call mtrace()' -p $pid# 一段时间后,调用muntrace敞开追踪$ gdb -q -batch -ex 'call muntrace()' -p $pid而后查看malloc_trace.log,内容如下: 能够发现,在开启mtrace后,glibc将所有malloc、free操作都记录了下来,通过从日志中找出哪些地方执行了malloc后没有free,即是内存泄露点。 于是glibc又提供了一个mtrace命令,其作用就是找出下面说的执行了malloc后没有free的记录,如下: $ mtrace malloc_trace.log | less -nMemory not freed:----------------- Address Size Caller0x00007efe08008cc0 0x18 at 0x7efe726e8e5d0x00007efe08008ea0 0x160 at 0x7efe726e8e5d0x00007efe6cabca40 0x58 at 0x7efe715dc4320x00007efe6caa9ad0 0x1bf8 at 0x7efe715e4b880x00007efe6caab6d0 0x1bf8 at 0x7efe715e4b880x00007efe6ca679c0 0x8000 at 0x7efe715e4947# 按Caller分组统计一下,看看各Caller各泄露的次数及内存量$ mtrace malloc_trace.log | sed '1,/Caller/d'|awk '{s[$NF]+=strtonum($2);n[$NF]++;}END{for(k in s){print k,n[k],s[k]}}'|column -t0x7efe715e4b88 1010 72316000x7efe715dc432 1010 888800x7efe715e4947 997 326696960x7efe726e8e5d 532 3098000x7efe715eb2f4 1 720x7efe715eb491 1 38能够发现,0x7efe715e4b88这个调用点,泄露了1010次,那怎么晓得这个调用点在哪个函数里呢? ...

September 23, 2023 · 2 min · jiezi

goroutine泄露原理场景检测和防范

如果你启动了一个 goroutine,但并没有符合预期的退出,直到程序结束,此goroutine才退出,这种情况就是 goroutine 泄露。当 goroutine 泄露发生时,该 goroutine 的栈(一般 2k 内存空间起)一直被占用不能释放,goroutine 里的函数在堆上申请的空间也不能被 垃圾回收器 回收。这样,在程序运行期间,内存占用持续升高,可用内存越来也少,最终将导致系统崩溃。 回顾一下 goroutine 终止的场景: 当一个goroutine完成它的工作由于发生了没有处理的错误有其他的协程告诉它终止那么当这三者同时没发生的时候,就会导致 goroutine 始终不会终止退出。 goroutine 泄露的场景goroutine泄露一般是因为channel操作阻塞而导致整个routine一直阻塞等待或者 goroutine 里有死循环的时候。可以细分为下面五种情况: 1. 从 channel 里读,但是没有写// leak 是一个有 bug 程序。它启动了一个 goroutine 阻塞接收 channel。当 Goroutine 正在等待时,leak 函数会结束返回。此时,程序的其他任何部分都不能通过 channel 发送数据,那个 channel 永远不会关闭,fmt.Println 调用永远不会发生, 那个 goroutine 会被永远锁死func leak() { ch := make(chan int) go func() { val := <-ch fmt.Println("We received a value:", val) }()}2. 向 unbuffered channel 写,但是没有读// 一个复杂一点的例子func sendMsg(msg, addr string) error { conn, err := net.Dial("tcp", addr) if err != nil { return err } defer conn.Close() _, err = fmt.Fprint(conn, msg) return err} func broadcastMsg(msg string, addrs []string) error { errc := make(chan error) for _, addr := range addrs { go func(addr string) { errc <- sendMsg(msg, addr) fmt.Println("done") }(addr) } for _ = range addrs { if err := <-errc; err != nil { return err } } return nil}func main() { addr := []string{"localhost:8080", "http://google.com"} err := broadcastMsg("hi", addr) time.Sleep(time.Second) if err != nil { fmt.Println(err) return } fmt.Println("everything went fine")}对于 broadcastMsg 里的这一段 ...

July 2, 2019 · 3 min · jiezi

Bitmap之位图采样和内存计算详解

原文首发于微信公众号:jzman-blog,欢迎关注交流!Android 开发中经常考虑的一个问题就是 OOM(Out Of Memory),也就是内存溢出,一方面大量加载图片时有可能出现 OOM, 通过采样压缩图片可避免 OOM,另一方面,如一张 1024 x 768 像素的图像被缩略显示在 128 x 96 的 ImageView 中,这种做法显然是不值得的,可通过采样加载一个合适的缩小版本到内存中,以减小内存的消耗,Bitmap 的优化主要有两个方面如下: 有效的处理较大的位图缓存位图这篇文章主要侧重于如何有效的处理较大的位图。 此外,在 Android 中按照位图采样的方法加载一个缩小版本到内存中应该考虑因素? 估计加载完整图像所需要的内存加载这个图片所需的空间带给其程序的其他内存需求加载图片的目标 ImageView 或 UI 组件的尺寸当前设备的屏幕尺寸或密度位图采样图像有不同的形状的和大小,读取较大的图片时会耗费内存。读取一个位图的尺寸和类型,为了从多种资源创建一个位图,BitmapFactory 类提供了许多解码的方法,根据图像数据资源选择最合适的解码方法,这些方法试图请求分配内存来构造位图,因此很容易导致 OOM 异常。每种类型的解码方法都有额外的特征可以让你通过 BitMapFactory.Options 类指定解码选项。当解码时设置 inJustDecodeBounds 为true,可在不分配内存之前读取图像的尺寸和类型,下面的代码实现了简单的位图采样: /** * 位图采样 * @param res * @param resId * @return */public Bitmap decodeSampleFromResource(Resources res, int resId){ //BitmapFactory创建设置选项 BitmapFactory.Options options = new BitmapFactory.Options(); //设置采样比例 options.inSampleSize = 200; Bitmap bitmap = BitmapFactory.decodeResource(res,resId,options); return bitmap;}注意:其他 decode... 方法与 decodeResource 类似,这里都以 decodeRedource 为例。 ...

June 20, 2019 · 5 min · jiezi

实战Go内存泄露

最近解决了我们项目中的一个内存泄露问题,事实再次证明pprof是一个好工具,但掌握好工具的正确用法,才能发挥好工具的威力,不然就算你手里有屠龙刀,也成不了天下第一,本文就是带你用pprof定位内存泄露问题。 关于Go的内存泄露有这么一句话不知道你听过没有: 10次内存泄露,有9次是goroutine泄露。我所解决的问题,也是goroutine泄露导致的内存泄露,所以这篇文章主要介绍Go程序的goroutine泄露,掌握了如何定位和解决goroutine泄露,就掌握了内存泄露的大部分场景。 本文草稿最初数据都是生产坏境数据,为了防止敏感内容泄露,全部替换成了demo数据,demo的数据比生产环境数据简单多了,更适合入门理解,有助于掌握pprof。go pprof基本知识定位goroutine泄露会使用到pprof,pprof是Go的性能工具,在开始介绍内存泄露前,先简单介绍下pprof的基本使用,更详细的使用给大家推荐了资料。 什么是pprofpprof是Go的性能分析工具,在程序运行过程中,可以记录程序的运行信息,可以是CPU使用情况、内存使用情况、goroutine运行情况等,当需要性能调优或者定位Bug时候,这些记录的信息是相当重要。 基本使用使用pprof有多种方式,Go已经现成封装好了1个:net/http/pprof,使用简单的几行命令,就可以开启pprof,记录运行信息,并且提供了Web服务,能够通过浏览器和命令行2种方式获取运行数据。 看个最简单的pprof的例子: 文件:golang_step_by_step/pprof/pprof/demo.go package mainimport ( "fmt" "net/http" _ "net/http/pprof")func main() { // 开启pprof,监听请求 ip := "0.0.0.0:6060" if err := http.ListenAndServe(ip, nil); err != nil { fmt.Printf("start pprof failed on %s\n", ip) }}提醒:本文所有代码部分可左右滑动 浏览器方式 输入网址ip:port/debug/pprof/打开pprof主页,从上到下依次是5类profile信息: block:goroutine的阻塞信息,本例就截取自一个goroutine阻塞的demo,但block为0,没掌握block的用法goroutine:所有goroutine的信息,下面的full goroutine stack dump是输出所有goroutine的调用栈,是goroutine的debug=2,后面会详细介绍。heap:堆内存的信息mutex:锁的信息threadcreate:线程信息这篇文章我们主要关注goroutine和heap,这两个都会打印调用栈信息,goroutine里面还会包含goroutine的数量信息,heap则是内存分配信息,本文用不到的地方就不展示了,最后推荐几篇文章大家去看。 命令行方式当连接在服务器终端上的时候,是没有浏览器可以使用的,Go提供了命令行的方式,能够获取以上5类信息,这种方式用起来更方便。 使用命令go tool pprof url可以获取指定的profile文件,此命令会发起http请求,然后下载数据到本地,之后进入交互式模式,就像gdb一样,可以使用命令查看运行信息,以下是5类请求的方式: # 下载cpu profile,默认从当前开始收集30s的cpu使用情况,需要等待30sgo tool pprof http://localhost:6060/debug/pprof/profile # 30-second CPU profilego tool pprof http://localhost:6060/debug/pprof/profile?seconds=120 # wait 120s# 下载heap profilego tool pprof http://localhost:6060/debug/pprof/heap # heap profile# 下载goroutine profilego tool pprof http://localhost:6060/debug/pprof/goroutine # goroutine profile# 下载block profilego tool pprof http://localhost:6060/debug/pprof/block # goroutine blocking profile# 下载mutex profilego tool pprof http://localhost:6060/debug/pprof/mutex上面的pprof/demo.go太简单了,如果去获取内存profile,几乎获取不到什么,换一个Demo进行内存profile的展示: ...

May 18, 2019 · 7 min · jiezi