从golang的垃圾回收说起(下篇)(代码片段)

163yun 163yun     2022-12-26     693

关键词:

文章来自网易云社区

 

4 Golang垃圾回收的相关参数

 

4.1 触发GC

 

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

 

 # 表示当前应用占用的内存是上次GC时占用内存的两倍时,触发GCexport GOGC=100

4.2 查看GC信息

 

export GODEBUG=gctrace=1

可以查看gctrace信息。

 

举例:

 

gc 1 @0.008s 6%: 0.071+2.0+0.080 ms clock, 0.21+0.22/1.9/1.9+0.24 ms cpu, 4->4->3 MB, 5 MB goal, 4 P# command-line-argumentsgc 1 @0.001s 16%: 0.071+3.3+0.060 ms clock, 0.21+0.17/2.9/0.36+0.18 ms cpu, 4->4->4 MB, 5 MB goal, 4 Pgc 2 @0.016s 8%: 0.020+6.0+0.070 ms clock, 0.082+0.094/3.9/2.2+0.28 ms cpu, 8->9->8 MB, 9 MB goal, 4 Pgc 3 @0.046s 7%: 0.019+7.3+0.062 ms clock, 0.076+0.089/7.1/7.0+0.24 ms cpu, 14->16->14 MB, 17 MB goal, 4 Pgc 4 @0.092s 8%: 0.015+24+0.10 ms clock, 0.060+0.10/24/0.75+0.42 ms cpu, 25->27->24 MB, 28 MB goal, 4 P

每个字段表示什么信息可以参考 golang doc

 

 

5 如何提高GC的性能

 

Golang的GC算法是固定的,用户无法去配置采用什么算法,也没法像Java一样配置年轻代、老年代的空间比例等。golang的GC相关的配置参数只有一个,即GOGC,用来表示触发GC的条件。

 

目前来看,提高GC效率我们唯一能做的就是减少垃圾的产生。所以说,这一章称为提高GC的性能也不太合适。下面我们就主要讨论一下,在golang中如何减少垃圾的产生,有哪些需要注意的方面。

 

5.1 golang中的内存分配

 

参考官网Frequently Asked Questions (FAQ)

 

How do I know whether a variable is allocated on the heap or the stack?

From a correctness standpoint, you don‘t need to know. Each variable in Go exists as long as there are references to it. The storage location chosen by the implementation is irrelevant to the semantics of the language.

 

The storage location does have an effect on writing efficient programs. When possible, the Go compilers will allocate variables that are local to a function in that function‘s stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.

 

In the current compilers, if a variable has its address taken, that variable is a candidate for allocation on the heap. However, a basic escape analysis recognizes some cases when such variables will not live past the return from the function and can reside on the stack.

我们看一个例子有个直观的认识:

 

1 package main2 3 import ()4 5 func foo() *int 6     var x int7     return &x8 9 10 func bar() int 11     x := new(int)12     *x = 113     return *x14 15 16 func big() 17     x := make([]int,0,20)18     y := make([]int,0,20000)19 20     len := 1021     z := make([]int,0,len)22 23 24 func main() 25  26 
# go build -gcflags=‘-m -l‘ test.go./test.go:7:12: &x escapes to heap
./test.go:6:9: moved to heap: x
./test.go:11:13: bar new(int) does not escape
./test.go:18:14: make([]int, 0, 20000) escapes to heap
./test.go:21:14: make([]int, 0, len) escapes to heap
./test.go:17:14: big make([]int, 0, 20) does not escape
./test.go:17:23: x declared and not used
./test.go:18:23: y declared and not used
./test.go:21:23: z declared and not used

5.2 sync.Pool对象池

 

sync.Pool主要是为了重用对象,一方面缩短了申请空间的时间,另一方面,还减轻了GC的压力。不过它是一个临时对象池,为什么这么说呢?因为对象池中的对象会被GC回收。所以说,有状态的对象,比如数据库连接是不能够用sync.Pool来实现的。

use sync.Pool if you frequently allocate many objects of the same type and you want to save some allocation and garbage collection overhead. However, in the current implementation, any unreferenced sync.Pool objects are removed at each garbage collection cycle, so you can‘t use this as a long-lived free-list of objects. If you want a free-list that maintains objects between GC runs, you‘ll still have to build that yourself. This is only to reuse allocated objects between garbage collection cycles.  

sync.Pool主要有两个方法:

 

func (p *Pool) Get() interface 
    ...
func (p *Pool) Put(x interface) 
    ...

Get方法是指从临时对象池中申请对象,put是指把不再使用的对象返回对象池,以便后续重用。如果我们在使用Get申请新对象时pool中没有可用的对象,那么就会返回nil,除非设置了sync.Pool的New func:

 

type Pool struct 

    ...    // New optionally specifies a function to generate
    // a value when Get would otherwise return nil.
    // It may not be changed concurrently with calls to Get.
    New func() interface

另外,我们不能对从对象池申请到的对象值做任何假设,可能是New新生成的,可能是被某个协程修改过放回来的。

 

一个比较好的使用sync.Pool的例子:

var DEFAULT_SYNC_POOL *SyncPool

func NewPool() *SyncPool 
    DEFAULT_SYNC_POOL = NewSyncPool(
        5,     
        30000, 
        2,     
    )
    return DEFAULT_SYNC_POOL


func Alloc(size int) []int64 
    return DEFAULT_SYNC_POOL.Alloc(size)


func Free(mem []int64) 
    DEFAULT_SYNC_POOL.Free(mem)


// SyncPool is a sync.Pool base slab allocation memory pool
type SyncPool struct 
    classes     []sync.Pool
    classesSize []int
    minSize     int
    maxSize     int


func NewSyncPool(minSize, maxSize, factor int) *SyncPool 
    n := 0
    for chunkSize := minSize; chunkSize <= maxSize; chunkSize *= factor 
        n++
    
    pool := &SyncPool
        make([]sync.Pool, n),
        make([]int, n),
        minSize, maxSize,
    
    n = 0
    for chunkSize := minSize; chunkSize <= maxSize; chunkSize *= factor 
        pool.classesSize[n] = chunkSize
        pool.classes[n].New = func(size int) func() interface 
            return func() interface 
                buf := make([]int64, size)
                return &buf
            
        (chunkSize)
        n++
    
    return pool


func (pool *SyncPool) Alloc(size int) []int64 
    if size <= pool.maxSize 
        for i := 0; i < len(pool.classesSize); i++ 
            if pool.classesSize[i] >= size 
                mem := pool.classes[i].Get().(*[]int64)
                // return (*mem)[:size]
                return (*mem)[:0]
            
        
    
    return make([]int64, 0, size)


func (pool *SyncPool) Free(mem []int64) 
    if size := cap(mem); size <= pool.maxSize 
        for i := 0; i < len(pool.classesSize); i++ 
            if pool.classesSize[i] >= size 
                pool.classes[i].Put(&mem)
                return
            
        
    

有一个开源的通用golang对象池实现,有兴趣的可以研究一下:Go Commons Pool,在此不再赘述。

 

5.3 append

 

我们先看一下append的基本用法。

 

nums:=make([]int,0,10)

创建切片,len=0,cap=10,底层实际上分配了10个元素大小的空间。在没有append数据的情况下,不能直接使用nums[index]。

 

 

nums:=make([]int,5,10)

创建切片,len=5,cap=10,底层实际上分配了10个元素大小的空间。在没有append数据的情况下,可以直接使用nums[index],index的范围是[0,4]。执行append操作的时候是从index=5的位置开始存储的。

 

 

nums := make([]int,5)

如果没有指定capacity,那么cap与len相等。

 

 

nums = append(nums,10)

执行append操作的时候,nums的地址可能会改变,因此需要利用其返回值重新设置nums。至于nums的地址会不会改变,取决于还没有空间来存储新的数据,如果没有空闲空间了,那就需要申请cap*2的空间,将数据复制过去。

 

因此,我们在使用append操作的时候,最好是设置一个比较合理的cap值,即根据自己的应用场景预申请大小合适的空间,避免无谓的不断重新申请新空间,这样可以减少GC的压力。

 

由append导致的内存飙升和GC压力过大这个问题,需要特别注意一下。

 

参考文献

1 https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)
2 http://legendtkl.com/2017/04/28/golang-gc/
3 https://github.com/golang/proposal/blob/master/design/17503-eliminate-rescan.md
4 https://lengzzz.com/note/gc-in-golang
5 https://making.pusher.com/golangs-real-time-gc-in-theory-and-practice/
6 https://blog.twitch.tv/gos-march-to-low-latency-gc-a6fa96f06eb7
7 https://golang.org/doc/faq
8 《垃圾回收的算法与实现》 中村成杨 相川光. 编著  

 

 

网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者李岚清授权发布。

 








从golang的垃圾回收说起(上篇)(代码片段)

本文来自网易云社区1垃圾回收中的重要概念1.1定义Incomputerscience,garbagecollection(GC)isaformofautomaticmemorymanagement.Thegarbagecollector,orjustcollector,attemptstoreclaimgarbage,ormemoryoccupiedbyobjectsthatarenolonger 查看详情

从golang的垃圾回收说起(上篇)(代码片段)

本文来自网易云社区1垃圾回收中的重要概念1.1定义Incomputerscience,garbagecollection(GC)isaformofautomaticmemorymanagement.Thegarbagecollector,orjustcollector,attemptstoreclaimgarbage,ormemoryoccupiedbyobjectsthatarenolonger 查看详情

jvm技术专题垃圾回收认知和调优的“思南(司南)“「下篇」(代码片段)

...主要区别是,并行收集器有多个线程,用于加速垃圾回收。通过命令行选项-XX:+UseParallelGC启用并行收集器。默认情况下,使用此选项,次要(minor)和主要(MajorGC)都将并行运行, 查看详情

jvm技术专题性能调优之cms垃圾回收器「下篇」(代码片段)

...,希望先去看一下上部【JVM技术专题】性能调优之CMS垃圾回收器「上篇」,本文承接上文内容,进行深入分析CMS原理以及调优和相关问题的分析等。学习背景关于CMSGC介绍和调优的文章比较多,但大多没有经过验... 查看详情

golang垃圾回收器与标记清除算法(代码片段)

go垃圾回收器的主要关注点是低延迟,也就是说为了进行实时操作它会有短暂的暂停。另一方面,创建新对象然后使用指针操作存活对象是程序始终在做的事情,这个过程可能最终会创建出不会再被访问到的对象,... 查看详情

聊聊javascript垃圾回收机制-v8引擎下的垃圾回收机制(代码片段)

引子从修真故事说起上文大概介绍了垃圾回收的机制和标记清除法的核心思路,接下来准备深入介绍下v8引擎里的垃圾回收算法。既然是算法类的介绍,那自然是比较枯燥的,如果想完全弄懂,可以收藏下来,多看几遍(!·_·!)... 查看详情

java垃圾回收机制详解(代码片段)

前言Java相比C/C++最显著的特点便是引入了自动垃圾回收(下文统一用GC指代自动垃圾回收),它解决了C/C++最令人头疼的内存管理问题,让程序员专注于程序本身,不用关心内存回收这些恼人的问题,这也是Java能大行其道的重要原... 查看详情

golang垃圾回收和setfinalizer(代码片段)

golang自带内存回收机制--GC。GC通过独立的进程执行,它会搜索不再使用的变量,并释放。需要注意的是,进行GC会占用机器资源。GC是自动进行的。如果要手动进行GC,可以调用runtime.GC()函数,进行显式GC。SetFinalizer一个对象object... 查看详情

从c和c++内存管理来谈谈jvm的垃圾回收算法设计-下(代码片段)

从C和C++内存管理来谈谈JVM的垃圾回收算法设计-下引言基本概念对象GCROOTS垃圾回收常见算法标记清除优缺点引用计数优缺点部分标记清除算法优缺点复制算法优缺点多空间复制算法标记整理(标记压缩)优缺点分代设计HotSpot... 查看详情

从c和c++内存管理来谈谈jvm的垃圾回收算法设计-上(代码片段)

从C和C++内存管理来谈谈JVM的垃圾回收算法设计-上引言C内存模型malloc堆内存分配过程malloc为什么结合使用brk和mmapmalloc如何通过内存池管理Heap区域垃圾收集器引言本文想和大家来探讨一下JVM是如何对堆内存进行管理和垃圾... 查看详情

jvm7大垃圾回收器下篇g1(garbagefirst)

...,所以才会不断地尝试对GC进行优化。G1(Garbage一First)垃圾回收器是在Java7update4之后引入的一个新的 查看详情

go语言入门——数组切片和映射(代码片段)

...loWorld太简单了。  1、简介 Go是什么?Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程 查看详情

从c和c++内存管理来谈谈jvm的垃圾回收算法设计-下(代码片段)

从C和C++内存管理来谈谈JVM的垃圾回收算法设计-下引言基本概念对象GCROOTS垃圾回收常见算法标记清除优缺点引用计数优缺点部分标记清除算法优缺点复制算法优缺点多空间复制算法标记整理(标记压缩)优缺点分代设计HotSpot... 查看详情

golang——垃圾回收gc

Go垃圾回收原理Golang源码探索(三)GC的实现原理引用计数:对每个对象维护一个引用计数,当引用该对象的对象被销毁时,引用计数减1,当引用计数器为0是回收该对象。优点:对象可以很快的被回收,不会出现内存耗尽或达到某... 查看详情

自动的内存管理系统实操手册——golang垃圾回收篇

导语 | 现代高级编程语言管理内存的方式分自动和手动两种。手动管理内存的典型代表是C和C++,编写代码过程中需要主动申请或者释放内存;而PHP、Java和Go等语言使用自动的内存管理系统,由内存分配器和垃... 查看详情

详解python的垃圾回收机制(代码片段)

python的垃圾回收机制一、引子我们定义变量会申请内存空间来存放变量的值,而内存的容量是有限的,当一个变量值没有用了(简称垃圾)就应该将其占用的内存空间给回收掉,而变量名是访问到变量值的唯一方式,所以当一个... 查看详情

垃圾回收与对象的引用(代码片段)

垃圾回收当程序创建对象、数组等引用类型实体时,系统就会在对内存中为之分配一块内存区,对象就保存在这块内存区中,当这块内存不再被任何引用变量引用时,这块内存就变成垃圾,等待垃圾回收机制进行回收。垃圾回收... 查看详情

jvm之垃圾回收器(代码片段)

1、GC分类与性能指标1.1、垃圾回收器概述垃圾收集器没有在规范中进行过多的规定,可以由不同的厂商、不同版本的JVM来实现。由于JDK的版本处于高速迭代过程中,因此Java发展至今已经衍生了众多的GC版本。从不同角度... 查看详情