golang java耗内存_记一次golang的内存泄露

程序功能

此程序的主要功能是将文件中数据导入到clickhouse数据库中。

【问题描述】

服务器内存每隔一段时间会耗尽

1576731298c927390268da35f6ae571a.png

【问题分析】

由于使用的是go语言开发的,所以采用了业界流行的工具pprof。

参考URL:https://cizixs.com/2017/09/11/profiling-golang-program/

工具的使用与思路:

1)先修改源代码

2)安装工具观察

3)根据工具抓取的现象进行分析

4)修复内存缺陷代码, 再根据分析结果修复内存泄漏的地方

5)发布代码进行再跟踪分析

==================================================

1)修改代码:

使用这个工具前需要在代码中写几行代码,以便能使用这个工具的来收集数据。

ed877f3d890053c30c8ce00c6f5e1e28.png

3e23bf944f685b575c4ed21838ad0e59.png

1 //引用pprof

2 import "net/http"

3 import_ "net/http/pprof"

4

5 //在主函数中新增端口监控程序

6 //由于我的代码本来就是守护进程,所以这里采用新开一个监听协程方式,防止阻塞

7 func main(){

8 go func(){

9 http.ListenAndServe("0.0.0.0:80", nil)

10 }()

11 //其他代码

12 ...

13 }

经过上面的源代码改造后,重新部署到服务器上,观察内存状况;

内存仍然可以重新持续消耗内存不释放的现象。

2)在服务器上安装 golang pprof 程序,进行数据采集。

安装方法:yum install golang pprof

3)使用命令对heap进行dump分析,这个工具的好处是dump后可以直接生成pdf或png

1 [root@centos ~]# go tool pprof /root/clickhouse_runner/clickhouse_mssql_e

tl http://0.0.0.0:80/debug/pprof/heap

2 Fetching profile over HTTP from http://0.0.0.0:80/debug/pprof/heap

3 Saved profile in /root/pprof/pprof.clickhouse_mssql_etl.alloc_objects.all

oc_space.inuse_objects.inuse_space.012.pb.gz

4 File: clickhouse_mssql_etl

5 Type: inuse_space

6 Time: Feb 5, 2020 at 4:15pm (CST)

7 Entering interactive mode (type "help" for commands, "o" for options)

8 (pprof) pdf

9 Generating report in profile003.pdf

10 (pprof) quit

11 [root@centos ~]

309faa5e6808d2baeb3cdbe53b003b95.png

d56db04f23f5d4392fef3ea8ac5a468f.png

通过上面的heap 来分析,可以很明显的看到代码中主要的内存使用地方在于clickhouse 的驱动中,调用clickhouse的部分在创建内存没有释放(后来仔细分析了下golang的内存gc逻辑是由于gc速度存在滞后现象,而导入程序创建速度又很快,所以才导致gc越来越慢)。

83a01c772d37017655ad010113b70a80.png

4)找到内存泄漏的源头,开始修改代码

修改前源代码:

1 connect, err := sql.Open("clickhouse", connstring)

2 if err != nil {

3 return err

4 }

5 load_start := time.Now()

6 tx, err := connect.Begin()

7 if err != nil {

8 log.Println(full_filename, "begin err", err)

9 return err

10 }

11 stmt, err := tx.Prepare("insert ... values....")

12 if err != nil {

13 log.Println(full_filename, "preare err", err)

14 return err

15 }

16 _, er := stmt.Exec(...)

17 if er != nil {

18 log.Println("err", er)

19 }

20 er2 := tx.Commit()

21 if er2 != nil {

22 log.Println(db_view, "err", er2)

23 }

24 stmt.Close()

25 connect.Close()

//通过自己写的代码与clickhouse 驱动代码的分析,总结可以有两种方式来改进内存泄

漏:

a.修改clickhouse中的驱动代码,再执行完代码后立即进行重置内存,而不等gc来处理:

1 func (stmt *stmt) Close() error {

2 stmt.ch.logf("[stmt] close")

3 //新增再次回收内存数据

4 if stmt.ch.block != nil {

5 stmt.ch.block.Reset()

6 }

7 return nil

8 }

b. 直接释放stmt的对象,利用gc 的自动回收(考虑后还是采用这个方式更合理些)

1 stmt.Close()

2 connect.Close()

3 //新增直接将stmt,connect对象置nil

4 //clear mem

5 stmt = nil

6 tx = nil

7 connect = nil

修改后完整的代码:

1 connect, err := sql.Open("clickhouse", connstring)

2 if err != nil {

3 return err

4 }

5 load_start := time.Now()

6 tx, err := connect.Begin()

7 if err != nil {

8 log.Println(full_filename, "begin err", err)

9 return err

10 }

11 stmt, err := tx.Prepare("insert ... values....")

12 if err != nil {

13 log.Println(full_filename, "preare err", err)

14 return err

15 }

16 _, er := stmt.Exec(...)

17 if er != nil {

18 log.Println("err", er)

19 }

20 er2 := tx.Commit()

21 if er2 != nil {

22 log.Println(db_view, "err", er2)

23 }

24 stmt.Close()

25 connect.Close()

26

27 //***** clear mem for gc ******

28 stmt = nil

29 tx = nil

30 connect = nil

31 //

5) 发布修改后的代码,进行观察,通过观察发现系统内存可以正常回收与释放

0e64a00e4c72ffcad6b6ea4a15d15fad.png

【结论】

经过本次golang的调试发生,真正的原因是gc内存释放不够及时,存在滞后性(通过其他服务器观察发现,当压力小的时候,内存是可以正常释放的)。

所以最佳实践还是,在涉及到golang中使用大对象或者频繁创建内存的时候,要采用将对象设置能obj = nil 的方式,告知gc 我已经确实不再使用该内存块了,以便gc快速的回收,减少迭代gc。

另外,这种方式是可以应用到如java,c# 等语言身上的,它们都存在类似的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值