美文网首页
记一次go内存消减的过程

记一次go内存消减的过程

作者: MR_Model | 来源:发表于2019-07-12 09:55 被阅读0次

go内存问题说明:

工作中,短暂接手了一个go编程的应用程序,其中有有一个升级组件,在升级过程、比较md5等场景中约占用内存500M。

使用工具gops、pprof

gops: goland Attach to Process 工具。通过gops可以连接到本地的进程中,进行断点调试。

获得途径:通过github 获得gops源码,然后进行编译,编译期间遇到几个问题通过修改源码中的go.mod一一修改,得到gops.exe。放在GoPath下,完成。

pprof:go通过 go tool pprof能够观察到go应用程序的CPU、Memory的情况,可以通过graphviz将 pprof中的得到的信息,形成可视化的图像,方便追踪问题。

初步探索:

问题原因是同事使用升级组件时,内存会飙涨,等到10分钟左右,内存才会降下来。

首先,我们从网上了解到,go是一个拥有gc的语言。同时,随着版本的更迭,go的gc愈加的完善。所以,不要轻易怀疑一种语言,怀疑自己的代码。

go的垃圾回收采用的是 标记-清理(Mark-and-Sweep)算法:先标记出需要回收的内存对象,然后清理掉。
go回收内存慢的原因可能有以下几个:
1.go在没有长时间触发gc的情况下,可能需要有一个时间间隔才会主动触发一次gc;
2.go的gc机制,不是立即将内存返回给系统,而是告诉系统,这些内存没有人使用了,但系统不会立即回收,而是延缓回收,已提供给go需要内存是更快的分配速度。

即,go大体上可以看做内含一个内存池的存在。猜测程序爆发500M的内存是因为峰值内存上去导致的。

初次交手

起初,在浏览go的资料时,关于string和[]byte的转换,是我比较关注的一个点。
go中 string和[]byte的相互转换的时候,需要重新分配一次内存,使用拷贝进行操作。

程序中,关于文件操作的Read、Write方法,使用了OS.WriteString等方法,通过string、[]byte的多次转换来实现功能。

结果:程序内存有下降,不过峰值下降不够明显,且不够稳定。

神来之笔 - pprof

仅仅通过外部的猜测,就想要解决程序的内存问题,显然有些天方夜谭。这个时候需要思考和一定顺手的工具。

pprof在这个时候进入我的视野。

.runtime/pprof:采集程序(非 Server)的运行数据进行分析
.net/http/pprof:采集 HTTP Server 的运行时数据进行分析

.net/http/pprof可以通过程序将数据发送到本地端口,可以利用本地浏览器进行可视化追踪。添加的代码如下:

import (
    _"net/http/pprof"
)

func main () {
    go func() {
        http.ListenAndServe("0.0.0.0:8899", nil)
    }()
    ...
}

随后启动程序,走流程测试。

cmd命令:go tool pprof -alloc_space/-inuse_space http://localhost:8899/debug/pprof/heap

执行结果如下图:


pprof-cmd.jpg

之后可以使用pprof的指令。 这里我使用的是 web,即打开本地浏览器,可以看到具体的内存流向图如下。


流向图.png

可以看出内存的保障和一个函数有关:bytes.makeSlice

罪魁祸首

根据pprof所得的流程图可以得到,在bytes.makeSlice的上一级调用时 ioutil.ReadAll。
找到ioutil.ReadAll的的源码,发现会调用buffer.grow这个底层增长函数。且在调用ioutil.ReadAll函数的地方,[]btyes的大小没有一个初始估计,导致go会通过*2的方式去申请足够大小的内存,读取数据,这一定程度上导致了内存的损耗。

措施:在ioutil.ReadAll函数调用前,先预估文件的大小,然后调用。
结果:内存下降了在170M左右。

结果

170M峰值内存,在目前来看是调节的一个极限,因为程序在运行过程中,会下载约100M的文件。而我观察到的下载方式是通过全部读取到内存中,然后再写入文件中。这个业务方式,直接导致峰值内存一定会在100M以上,而且还有协成下载和其他的一些内存使用。

内存优化,暂时告一段落。

心得

这是我初步接手go的程序,短短2周不到的了解,就可以看到内存上面处理问题的诸多不足。这些有可能是因为前人的逻辑漏洞,也有很多是go本身底层的东西就没有看透。
只想说:
1.语言无对错,错在使用方法上
2.不是说高并发就是好的,真正的高并发也是应该考虑实际应用环境才可以的。
3.遇到事情,不能想什么其他的外路(ps:比如内存高就重启之类的)

以上是此次解决问题的一些想法。问题很简单,重要的是编程思路和不断的学习+测试,记录下来给以后的自己一个警醒。

最后贴一个获得md5值的go代码,大家可以看看他们的内存差别。

  1.  f, err := os.Open(file)
         if err != nil {
            return false
         }
     h := md5.New()
     io.Copy(h,f)
     dst := string(h.Sum(nil)[:16])
    
  2.  data, err := ioutil.ReadFile(file)
     if err != nil {
         return
     }
     value = md5.Sum(data)
     return value, nil

相关文章

  • 记一次go内存消减的过程

    go内存问题说明: 工作中,短暂接手了一个go编程的应用程序,其中有有一个升级组件,在升级过程、比较md5等场景中...

  • go-内存机制(3)

    go的内存分配 Golang有一套自己的内存管理机制,自主的去完成内存分配、垃圾回收、内存管理等过程,从而避免频繁...

  • Go语言——内存管理

    Go语言——内存管理 参考: 图解 TCMalloc Golang 内存管理 Go 内存管理 问题 内存碎片:避免...

  • 图解 Go 内存管理器的内存分配策略

    关于Go的内存分配 在 Go 语言里,从内存的分配到不再使用后内存的回收等等这些内存管理工作都是由 Go 在底层完...

  • 记一次内存泄漏定位过程

    01 机器内存告警,查看jvm内存及gc情况 老年代占比接近90% S0 S1 E O ...

  • iOS笔记-记录一次内存泄漏发现过程

    iOS笔记-记录一次内存泄漏发现过程 iOS笔记-记录一次内存泄漏发现过程

  • go 内存模型简要说明

    go 内存模型 大体上来说go的内存是先申请一大片内存,然后将内存分为各个小的span来管理,因为每个go对象有对...

  • 转载:探寻 Redis 内存诡异增长的元凶

    转载:探寻 Redis 内存诡异增长的元凶 记一次 Redis 内存诡异增长,由于 一次RedisRehash 造...

  • 内存泄露

    内存泄露 实战 实战Go内存泄露 - Go语言实战 - SegmentFault 思否 总结 pprof工具 使用...

  • JVM问题排查实战系列

    JVM问题排查实战 记一次频繁FGC的简单排查 一次JVM GC长暂停的排查过程 如何使用MAT进行内存泄露分析

网友评论

      本文标题:记一次go内存消减的过程

      本文链接:https://www.haomeiwen.com/subject/ligskctx.html