42

golang垃圾回收

 5 years ago
source link: https://studygolang.com/articles/15864?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

GC算法简介

查看: Golang 垃圾回收剖析 中GC算法简介

GC性能的评价标准

摘自: https://studygolang.com/artic...

  • 吞吐量:是指单位时间内是有多少时间是用来运行user application的。GC占用的时间过多,就会导致吞吐量较低。
  • 最大暂停时间:基本上所有的垃圾回收算法,都会在执行GC的过程中,暂停user application。如果暂停时间过长,必然会影响用户体验,尤其是那些交互性较强的应用。
  • 堆使用效率:影响堆使用效率的主要有两个因素,一个是对象头部大小,一个是堆的用法。一般来说,堆的头部越大,存储的信息越多,那么GC的效率就会越高,吞吐量什么的也会有更佳的表现。但是,我们必须明白,对象头越小越好。另外,不同的算法对于堆的不同用法,也会导致堆使用效率差距非常大。比如复制算法,用户应用只能使用一般的堆大小。GC是自动管理内存的,如果因为GC导致过量占用堆,那么就是本末倒置了。
  • 访问的局部性:具有引用关系的对象之间很可能存在连续访问的情况。因此,把具有引用关系的对象安排在堆中较近的位置,可以充分利用内存访问局部性。有的GC算法会根据引用关系重排对象,比如复制算法。

设计垃圾回收算法时,折中无处不在。较大的吞吐量和较短的最大暂停时间往往不可兼得。

写屏障

golang采用三色法作为 GC 的计算方式, 对于已经扫描过的对象, 如果检测是否由于用户逻辑的变化而引起的数据变化呢, golang中采用了写屏障的方式, 对扫描过后的对象使⽤操作系统写屏障功能⽤来监控⽤户逻辑这段内存。任何时候这段内存发⽣引⽤改变的时候就会造成写屏障发⽣⼀个信号,垃圾回收器会捕获到这样的信号后就知道这个对象发⽣改变,然后重新扫描这个对象,看看它的引⽤或者被引⽤是否被改变,这样利⽤状态的重置从⽽实现当对象状态发⽣改变的时候依然可以判断它是活着的还是死的

如何提高GC的性能

触发GC

GC触发的时机:2分钟或者内存占用达到一个阈值(当前堆内存占用是上次gc后对内存占用的两倍,当GOGC=100时)

GC的总时间

Tgc = Tseq + Tmark + Tsweep (T表示time)

Tseq 表示是停止用户的 goroutine 和做一些准备活动(通常很小)需要的时间

Tmark 是堆标记时间,标记发生在所有用户 goroutine 停止时,因此可以显著地影响处理的延迟

Tsweep 是堆清除时间,清除通常与正常的程序运行同时发生,所以对延迟来说是不太关键的

1. goroutine 被停止后, GC 将要开始的是时候会做一些准备工作,如写屏障设置等会执行STW

2. re-scan 的时候执行STW,停止用户程序,检验已经扫描的元素是否发生引用的变化

当前GC的算法是固定的, 用户不能够配置垃圾回收的算法,唯一能够更改就是垃圾回收的阀值, 即 GOGC , 用来表示触发GC的条件。当前能够提升垃圾回收效率的唯一方式就是 减少垃圾 的产生,

可通过下面的方式

  • 内存分配合理
  • sync.Pool 对象池,重复使用对象, 减少内存分配
  • append 使用, 提前设置 cap 的数量, 避免无故扩容

辅助回收

主要针对回收能力小于程序处理能力, 需要处理程序计算的资源转而处理垃圾回收.

实践中的一些问题

1.当停止大量的请求之后, 内存使用量并没有立即停止下来

原因可能如下:

一是go的垃圾回收有个触发阈值,这个阈值会随着每次内存使用变大而逐渐增大(如初始阈值是10MB则下一次就是20MB,再下一次就成为了40MB…),如果长时间没有触发gc go会主动触发一次(2min)。高峰时内存使用量上去后,除非持续申请内存,靠阈值触发gc已经基本不可能,而是要等最多2min主动gc开始才能触发gc。

第二个原因是go语言在向系统交还内存时只是告诉系统这些内存不需要使用了,可以回收;同时操作系统会采取“拖延症”策略,并不是立即回收,而是等到系统内存紧张时才会开始回收这样该程序又重新申请内存时就可以获得极快的分配速度。

2.gc时间长的问题

尽量避免频繁创建临时堆对象(如&abc{}, new, make等)以减少垃圾收集时的扫描时间,对于需要频繁使用的临时对象考虑直接通过数组缓存进行重用

3.goroutine泄露的问题

我们的一个服务需要处理很多长连接请求,实现时,对于每个长连接请求各开了一个读取和写入协程,全部采用endless for loop不停地处理收发数据。当连接被远端关闭后,如果不对这两个协程做处理,他们依然会一直运行,并且占用的channel也不会被释放…这里就必须十分注意,在不使用协程后一定要把他依赖的channel close并通过再协程中判断channel是否关闭以保证其退出。

4.少量使用+连接string

+ 来进行string的连接会生成新的对象,降低gc的效率,好的方式是通过append函数来进行。

5.string与[]byte转化

在stirng与[]byte之间进行转换,会给gc造成压力 通过gdb,可以先对比下两者的数据结构:

type = struct []uint8 {    uint8 *array;    int len;    int cap;}type = struct string {    uint8 *str;    int len;}

两者发生转换的时候,底层数据结结构会进行复制,因此导致gc效率会变低。

解决策略上,一种方式是一直使用[]byte,特别是在数据传输方面,[]byte中也包含着许多string会常用到的有效的操作。

另一种是使用更为底层的操作直接进行转化,避免复制行为的发生。主要是使用unsafe.Pointer直接进行转化

内存泄漏

当一个进程运行时分配了大量的内存, 但是由于程序设计上的问题, 导致分配的内存不能够良好的释放, 从而造成内存的浪费, 影响其他的进程使用.

对于C和C++这种没有Garbage Collection 的语言来讲,我们主要关注两种类型的内存泄漏:

堆内存泄漏(Heap leak), 对内存指的是程序运行中根据需要分配通过 malloc , realloc , new 等从堆中分配的一块内存,再是完成后必须通过调用对应的 free 或者 delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.

系统资源泄露(Resource Leak), 主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。

参考文档


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK