Golang 垃圾回收机制

1. Golang GC 发展

  Golang 从第一个版本以来,GC 一直是大家诟病最多的。但是每一个版本的发布基本都伴随着 GC 的改进。下面列出一些比较重要的改动。

  • v1.1 STW
  • v1.3 Mark STW, Sweep 并行
  • v1.5 三色标记法
  • v1.8 hybrid write barrier

2. GC 算法简介
  这一小节介绍三种经典的 GC 算法:

  • 引用计数(reference counting)
  • 标记-清扫(mark & sweep)
  • 节点复制(Copying Garbage Collection)
  • 分代收集(Generational Garbage Collection)

 


 3. 引用计数

  引用计数的思想非常简单:每个单元维护一个域,保存其它单元指向它的引用数量(类似有向图的入度)。当引用数量为 0 时,将其回收。引用计数是渐进式的,能够将内存管理的开销分布到整个程序之中。C++ 的 share_ptr 使用的就是引用计算方法。

  引用计数算法实现一般是把所有的单元放在一个单元池里,比如类似 free list。这样所有的单元就被串起来了,就可以进行引用计数了。新分配的单元计数值被设置为 1(注意不是 0,因为申请一般都说 ptr = new object 这种)。每次有一个指针被设为指向该单元时,该单元的计数值加 1;而每次删除某个指向它的指针时,它的计数值减 1。当其引用计数为 0 的时候,该单元会被进行回收。虽然这里说的比较简单,实现的时候还是有很多细节需要考虑,比如删除某个单元的时候,那么它指向的所有单元都需要对引用计数减 1。那么如果这个时候,发现其中某个指向的单元的引用计数又为 0,那么是递归的进行还是采用其他的策略呢?递归处理的话会导致系统颠簸。关于这些细节这里就不讨论了,可以参考文章后面的给的参考资料。

优点

  1. 渐进式。内存管理与用户程序的执行交织在一起,将 GC 的代价分散到整个程序。不像标记-清扫算法需要 STW (Stop The World,GC 的时候挂起用户程序)。
  2. 算法易于实现。
  3. 内存单元能够很快被回收。相比于其他垃圾回收算法,堆被耗尽或者达到某个阈值才会进行垃圾回收。

缺点

  1. 原始的引用计数不能处理循环引用。大概这是被诟病最多的缺点了。不过针对这个问题,也除了很多解决方案,比如强引用等。
  2. 维护引用计数降低运行效率。内存单元的更新删除等都需要维护相关的内存单元的引用计数,相比于一些追踪式的垃圾回收算法并不需要这些代价。
  3. 单元池 free list 实现的话不是 cache-friendly 的,这样会导致频繁的 cache miss,降低程序运行效率。

4. 标记-清扫
  标记-清扫算法是第一种自动内存管理,基于追踪的垃圾收集算法。算法思想在 70 年代就提出了,是一种非常古老的算法。内存单元并不会在变成垃圾立刻回收,而是保持不可达状态,直到到达某个阈值或者固定时间长度。这个时候系统会挂起用户程序,也就是 STW,转而执行垃圾回收程序。垃圾回收程序对所有的存活单元进行一次全局遍历确定哪些单元可以回收。算法分两个部分:标记(mark)和清扫(sweep)。标记阶段表明所有的存活单元,清扫阶段将垃圾单元回收。可视化可以参考下图。

  标记-清扫算法

  标记-清扫算法的优点也就是基于追踪的垃圾回收算法具有的优点:避免了引用计数算法的缺点(不能处理循环引用,需要维护指针)。缺点也很明显,需要 STW。

三色标记算法

  三色标记算法是对标记阶段的改进,原理如下:

  1. 起初所有对象都是白色。
  2. 从根出发扫描所有可达对象,标记为灰色,放入待处理队列。
  3. 从队列取出灰色对象,将其引用对象标记为灰色放入队列,自身标记为黑色。
  4. 重复 3,直到灰色对象队列为空。此时白色对象即为垃圾,进行回收。

  可视化如下。

  三色标记算法

  三色标记的一个明显好处是能够让用户程序和 mark 并发的进行,具体可以参考论文:《On-the-fly garbage collection: an exercise in cooperation.》。Golang 的 GC 实现也是基于这篇论文,后面再具体说明。

5. 节点复制
  节点复制也是基于追踪的算法。其将整个堆等分为两个半区(semi-space),一个包含现有数据,另一个包含已被废弃的数据。节点复制式垃圾收集从切换(flip)两个半区的角色开始,然后收集器在老的半区,也就是 Fromspace 中遍历存活的数据结构,在第一次访问某个单元时把它复制到新半区,也就是 Tospace 中去。在 Fromspace 中所有存活单元都被访问过之后,收集器在 Tospace 中建立一个存活数据结构的副本,用户程序可以重新开始运行了。

优点

  1. 所有存活的数据结构都缩并地排列在 Tospace 的底部,这样就不会存在内存碎片的问题。
  2. 获取新内存可以简单地通过递增自由空间指针来实现。

缺点

  1. 内存得不到充分利用,总有一半的内存空间处于浪费状态。

6. 分代收集
  基于追踪的垃圾回收算法(标记-清扫、节点复制)一个主要问题是在生命周期较长的对象上浪费时间(长生命周期的对象是不需要频繁扫描的)。同时,内存分配存在这么一个事实 “most object die young”。基于这两点,分代垃圾回收算法将对象按生命周期长短存放到堆上的两个(或者更多)区域,这些区域就是分代(generation)。对于新生代的区域的垃圾回收频率要明显高于老年代区域。

  分配对象的时候从新生代里面分配,如果后面发现对象的生命周期较长,则将其移到老年代,这个过程叫做 promote。随着不断 promote,最后新生代的大小在整个堆的占用比例不会特别大。收集的时候集中主要精力在新生代就会相对来说效率更高,STW 时间也会更短。

优点

  1. 性能更优。

缺点

  1. 实现复杂

  


 7. Golang GC

7.1 Overview
  在说 Golang 的具体垃圾回收流程时,我们先来看一下几个基本的问题。

  1. 何时触发 GC
  在堆上分配大于 32K byte 对象的时候进行检测此时是否满足垃圾回收条件,如果满足则进行垃圾回收。

 1 func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
 2     ...
 3     shouldhelpgc := false
 4     // 分配的对象小于 32K byte
 5     if size <= maxSmallSize {
 6         ...
 7     } else {
 8         shouldhelpgc = true
 9         ...
10     }
11     ...
12     // gcShouldStart() 函数进行触发条件检测
13     if shouldhelpgc && gcShouldStart(false) {
14         // gcStart() 函数进行垃圾回收
15         gcStart(gcBackgroundMode, false)
16     }
17 }
View Code

  上面是自动垃圾回收,还有一种是主动垃圾回收,通过调用 runtime.GC(),这是阻塞式的。

1 // GC runs a garbage collection and blocks the caller until the
2 // garbage collection is complete. It may also block the entire
3 // program.
4 func GC() {
5     gcStart(gcForceBlockMode, false)
6 }
View Code

  2. GC 触发条件
  触发条件主要关注下面代码中的中间部分:forceTrigger || memstats.heap_live >= memstats.gc_trigger 。forceTrigger 是 forceGC 的标志;后面半句的意思是当前堆上的活跃对象大于我们初始化时候设置的 GC 触发阈值。在 malloc 以及 free 的时候 heap_live 会一直进行更新,这里就不再展开了。

 1 // gcShouldStart returns true if the exit condition for the _GCoff
 2 // phase has been met. The exit condition should be tested when
 3 // allocating.
 4 //
 5 // If forceTrigger is true, it ignores the current heap size, but
 6 // checks all other conditions. In general this should be false.
 7 func gcShouldStart(forceTrigger bool) bool {
 8     return gcphase == _GCoff && (forceTrigger || memstats.heap_live >= memstats.gc_trigger) && memstats.enablegc && panicking == 0 && gcpercent >= 0
 9 }
10 
11 //初始化的时候设置 GC 的触发阈值
12 func gcinit() {
13     _ = setGCPercent(readgogc())
14     memstats.gc_trigger = heapminimum
15     ...
16 }
17 // 启动的时候通过 GOGC 传递百分比 x
18 // 触发阈值等于 x * defaultHeapMinimum (defaultHeapMinimum 默认是 4M)
19 func readgogc() int32 {
20     p := gogetenv("GOGC")
21     if p == "off" {
22         return -1
23     }
24     if n, ok := atoi32(p); ok {
25         return n
26     }
27     return 100
28 }
View Code

  3. 垃圾回收的主要流程
  三色标记法,主要流程如下:

  • 所有对象最开始都是白色。
  • 从 root 开始找到所有可达对象,标记为灰色,放入待处理队列。
  • 遍历灰色对象队列,将其引用对象标记为灰色放入待处理队列,自身标记为黑色。
  • 处理完灰色对象队列,执行清扫工作。

  详细的过程如下图所示,具体可参考 [9]。  

  关于上图有几点需要说明的是。

  1. 首先从 root 开始遍历,root 包括全局指针和 goroutine 栈上的指针。
  2. mark 有两个过程。
    1. 从 root 开始遍历,标记为灰色。遍历灰色队列。
    2. re-scan 全局指针和栈。因为 mark 和用户程序是并行的,所以在过程 1 的时候可能会有新的对象分配,这个时候就需要通过写屏障(write barrier)记录下来。re-scan 再完成检查一下。
  3. Stop The World 有两个过程。
    1. 第一个是 GC 将要开始的时候,这个时候主要是一些准备工作,比如 enable write barrier。
    2. 第二个过程就是上面提到的 re-scan 过程。如果这个时候没有 stw,那么 mark 将无休止。

  另外针对上图各个阶段对应 GCPhase 如下:

    • Off: _GCoff
    • Stack scan ~ Mark: _GCmark
    • Mark termination: _GCmarktermination

  7.2 写屏障 (write barrier)
  关于 write barrier,完全可以另外写成一篇文章,所以这里只简单介绍一下,这篇文章的重点还是 Golang 的 GC。垃圾回收中的 write barrier 可以理解为编译器在写操作时特意插入的一段代码,对应的还有 read barrier。

  为什么需要 write barrier,很简单,对于和用户程序并发运行的垃圾回收算法,用户程序会一直修改内存,所以需要记录下来。

  Golang 1.7 之前的 write barrier 使用的经典的 Dijkstra-style insertion write barrier [Dijkstra ‘78], STW 的主要耗时就在 stack re-scan 的过程。自 1.8 之后采用一种混合的 write barrier 方式 (Yuasa-style deletion write barrier [Yuasa ‘90] 和 Dijkstra-style insertion write barrier [Dijkstra ‘78])来避免 re-scan。具体的可以参考 17503-eliminate-rescan。

  7.3 标记
  下面的源码还是基于 go1.8rc3。这个版本的 GC 代码相比之前改动还是挺大的,我们下面尽量只关注主流程。垃圾回收的代码主要集中在函数 gcStart() 中。

1 // gcStart 是 GC 的入口函数,根据 gcMode 做处理。
2 // 1. gcMode == gcBackgroundMode(后台运行,也就是并行), _GCoff -> _GCmark
3 // 2. 否则 GCoff -> _GCmarktermination,这个时候就是主动 GC 
4 func gcStart(mode gcMode, forceTrigger bool) {
5     ...
6 }
View Code

  1. STW phase 1
  在 GC 开始之前的准备工作。

 1 func gcStart(mode gcMode, forceTrigger bool) {
 2     ...
 3     //在后台启动 mark worker 
 4     if mode == gcBackgroundMode {
 5         gcBgMarkStartWorkers()
 6     }
 7     ...
 8     // Stop The World
 9     systemstack(stopTheWorldWithSema)
10     ...
11     if mode == gcBackgroundMode {
12         // GC 开始前的准备工作
13 
14         //处理设置 GCPhase,setGCPhase 还会 enable write barrier
15         setGCPhase(_GCmark)
16           
17         gcBgMarkPrepare() // Must happen before assist enable.
18         gcMarkRootPrepare()
19 
20         // Mark all active tinyalloc blocks. Since we're
21         // allocating from these, they need to be black like
22         // other allocations. The alternative is to blacken
23         // the tiny block on every allocation from it, which
24         // would slow down the tiny allocator.
25         gcMarkTinyAllocs()
26           
27         // Start The World
28         systemstack(startTheWorldWithSema)
29     } else {
30         ...
31     }
32 }
View Code

  2. Mark
  Mark 阶段是并行的运行,通过在后台一直运行 mark worker 来实现。

 1 func gcStart(mode gcMode, forceTrigger bool) {
 2     ...
 3     //在后台启动 mark worker 
 4     if mode == gcBackgroundMode {
 5         gcBgMarkStartWorkers()
 6     }
 7 }
 8 
 9 func gcBgMarkStartWorkers() {
10     // Background marking is performed by per-P G's. Ensure that
11     // each P has a background GC G.
12     for _, p := range &allp {
13         if p == nil || p.status == _Pdead {
14             break
15         }
16         if p.gcBgMarkWorker == 0 {
17             go gcBgMarkWorker(p)
18             notetsleepg(&work.bgMarkReady, -1)
19             noteclear(&work.bgMarkReady)
20         }
21     }
22 }
23 // gcBgMarkWorker 是一直在后台运行的,大部分时候是休眠状态,通过 gcController 来调度
24 func gcBgMarkWorker(_p_ *p) {
25     for {
26         // 将当前 goroutine 休眠,直到满足某些条件
27         gopark(...)
28         ...
29         // mark 过程
30         systemstack(func() {
31         // Mark our goroutine preemptible so its stack
32         // can be scanned. This lets two mark workers
33         // scan each other (otherwise, they would
34         // deadlock). We must not modify anything on
35         // the G stack. However, stack shrinking is
36         // disabled for mark workers, so it is safe to
37         // read from the G stack.
38         casgstatus(gp, _Grunning, _Gwaiting)
39         switch _p_.gcMarkWorkerMode {
40         default:
41             throw("gcBgMarkWorker: unexpected gcMarkWorkerMode")
42         case gcMarkWorkerDedicatedMode:
43             gcDrain(&_p_.gcw, gcDrainNoBlock|gcDrainFlushBgCredit)
44         case gcMarkWorkerFractionalMode:
45             gcDrain(&_p_.gcw, gcDrainUntilPreempt|gcDrainFlushBgCredit)
46         case gcMarkWorkerIdleMode:
47             gcDrain(&_p_.gcw, gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit)
48         }
49         casgstatus(gp, _Gwaiting, _Grunning)
50         })
51         ...
52     }
53 }
View Code

  Mark 阶段的标记代码主要在函数 gcDrain() 中实现。

 1 // gcDrain scans roots and objects in work buffers, blackening grey
 2 // objects until all roots and work buffers have been drained.
 3 func gcDrain(gcw *gcWork, flags gcDrainFlags) {
 4     ...    
 5     // Drain root marking jobs.
 6     if work.markrootNext < work.markrootJobs {
 7         for !(preemptible && gp.preempt) {
 8             job := atomic.Xadd(&work.markrootNext, +1) - 1
 9             if job >= work.markrootJobs {
10                 break
11             }
12             markroot(gcw, job)
13             if idle && pollWork() {
14                 goto done
15             }
16         }
17     }
18       
19     // 处理 heap 标记
20     // Drain heap marking jobs.
21     for !(preemptible && gp.preempt) {
22         ...
23         //从灰色列队中取出对象
24         var b uintptr
25         if blocking {
26             b = gcw.get()
27         } else {
28             b = gcw.tryGetFast()
29             if b == 0 {
30                 b = gcw.tryGet()
31             }
32         }
33         if b == 0 {
34             // work barrier reached or tryGet failed.
35             break
36         }
37         //扫描灰色对象的引用对象,标记为灰色,入灰色队列
38         scanobject(b, gcw)
39     }
40 }
View Code

  3. Mark termination (STW phase 2)
  mark termination 阶段会 stop the world。函数实现在 gcMarkTermination()。1.8 版本已经不会再对 goroutine stack 进行 re-scan 了。细节有点多,这里不细说了。

 1 func gcMarkTermination() {
 2     // World is stopped.
 3     // Run gc on the g0 stack. We do this so that the g stack
 4     // we're currently running on will no longer change. Cuts
 5     // the root set down a bit (g0 stacks are not scanned, and
 6     // we don't need to scan gc's internal state).  We also
 7     // need to switch to g0 so we can shrink the stack.
 8     systemstack(func() {
 9         gcMark(startTime)
10         // Must return immediately.
11         // The outer function's stack may have moved
12         // during gcMark (it shrinks stacks, including the
13         // outer function's stack), so we must not refer
14         // to any of its variables. Return back to the
15         // non-system stack to pick up the new addresses
16         // before continuing.
17     })
18     ...
19 }
View Code

  7.4 清扫
  清扫相对来说就简单很多了。

 1 func gcSweep(mode gcMode) {
 2     ...
 3     //阻塞式
 4     if !_ConcurrentSweep || mode == gcForceBlockMode {
 5         // Special case synchronous sweep.
 6         ...
 7         // Sweep all spans eagerly.
 8         for sweepone() != ^uintptr(0) {
 9             sweep.npausesweep++
10         }
11         // Do an additional mProf_GC, because all 'free' events are now real as well.
12         mProf_GC()
13         mProf_GC()
14         return
15     }
16       
17     // 并行式
18     // Background sweep.
19     lock(&sweep.lock)
20     if sweep.parked {
21         sweep.parked = false
22         ready(sweep.g, 0, true)
23     }
24     unlock(&sweep.lock)
25 }
View Code

  对于并行式清扫,在 GC 初始化的时候就会启动 bgsweep(),然后在后台一直循环。

 1 func bgsweep(c chan int) {
 2     sweep.g = getg()
 3 
 4     lock(&sweep.lock)
 5     sweep.parked = true
 6     c <- 1
 7     goparkunlock(&sweep.lock, "GC sweep wait", traceEvGoBlock, 1)
 8 
 9     for {
10         for gosweepone() != ^uintptr(0) {
11             sweep.nbgsweep++
12             Gosched()
13         }
14         lock(&sweep.lock)
15         if !gosweepdone() {
16             // This can happen if a GC runs between
17             // gosweepone returning ^0 above
18             // and the lock being acquired.
19             unlock(&sweep.lock)
20             continue
21         }
22         sweep.parked = true
23         goparkunlock(&sweep.lock, "GC sweep wait", traceEvGoBlock, 1)
24     }
25 }
26 
27 func gosweepone() uintptr {
28     var ret uintptr
29     systemstack(func() {
30         ret = sweepone()
31     })
32     return ret
33 }
View Code

不管是阻塞式还是并行式,都是通过 sweepone()函数来做清扫工作的。如果对于上篇文章 Golang 内存管理 熟悉的话,这个地方就很好理解。内存管理都是基于 span 的,mheap_ 是一个全局的变量,所有分配的对象都会记录在 mheap_ 中。在标记的时候,我们只要找到对对象对应的 span 进行标记,清扫的时候扫描 span,没有标记的 span 就可以回收了。

 1 // sweeps one span
 2 // returns number of pages returned to heap, or ^uintptr(0) if there is nothing to sweep
 3 func sweepone() uintptr {
 4     ...
 5     for {
 6         s := mheap_.sweepSpans[1-sg/2%2].pop()
 7         ...
 8         if !s.sweep(false) {
 9             // Span is still in-use, so this returned no
10             // pages to the heap and the span needs to
11             // move to the swept in-use list.
12             npages = 0
13         }
14     }
15 }
16 
17 // Sweep frees or collects finalizers for blocks not marked in the mark phase.
18 // It clears the mark bits in preparation for the next GC round.
19 // Returns true if the span was returned to heap.
20 // If preserve=true, don't return it to heap nor relink in MCentral lists;
21 // caller takes care of it.
22 func (s *mspan) sweep(preserve bool) bool {
23     ...
24 }
View Code

  7.5 其他
  1. gcWork
  这里介绍一下任务队列,或者说灰色对象管理。每个 P 上都有一个 gcw 用来管理灰色对象(get 和 put),gcw 的结构就是 gcWork。gcWork 中的核心是 wbuf1 和 wbuf2,里面存储就是灰色对象,或者说是 work(下面就全部统一叫做 work)。

 1 type p struct {
 2     ...
 3     gcw gcWork
 4 }
 5 
 6 type gcWork struct {
 7     // wbuf1 and wbuf2 are the primary and secondary work buffers.
 8     wbuf1, wbuf2 wbufptr
 9   
10     // Bytes marked (blackened) on this gcWork. This is aggregated
11     // into work.bytesMarked by dispose.
12     bytesMarked uint64
13 
14     // Scan work performed on this gcWork. This is aggregated into
15     // gcController by dispose and may also be flushed by callers.
16     scanWork int64
17 }
View Code

  既然每个 P 上有一个 work buffer,那么是不是还有一个全局的 work list 呢?是的。通过在每个 P 上绑定一个 work buffer 的好处和 cache 一样,不需要加锁。

1 var work struct {
2     full  uint64                   // lock-free list of full blocks workbuf
3     empty uint64                   // lock-free list of empty blocks workbuf
4     pad0  [sys.CacheLineSize]uint8 // prevents false-sharing between full/empty and nproc/nwait
5     ...
6 }
View Code

  那么为什么使用两个 work buffer (wbuf1 和 wbuf2)呢?我下面举个例子。比如我现在要 get 一个 work 出来,先从 wbuf1 中取,wbuf1 为空的话则与 wbuf2 swap 再 get。在其他时间将 work buffer 中的 full 或者 empty buffer 移到 global 的 work 中。这样的好处在于,在 get 的时候去全局的 work 里面取(多个 goroutine 去取会有竞争)。这里有趣的是 global 的 work list 是 lock-free 的,通过原子操作 cas 等实现。下面列举几个函数看一下 gcWrok。

  初始化。

1 func (w *gcWork) init() {
2     w.wbuf1 = wbufptrOf(getempty())
3     wbuf2 := trygetfull()
4     if wbuf2 == nil {
5         wbuf2 = getempty()
6     }
7     w.wbuf2 = wbufptrOf(wbuf2)
8 }
View Code

  put。

 1 // put enqueues a pointer for the garbage collector to trace.
 2 // obj must point to the beginning of a heap object or an oblet.
 3 func (w *gcWork) put(obj uintptr) {
 4     wbuf := w.wbuf1.ptr()
 5     if wbuf == nil {
 6         w.init()
 7         wbuf = w.wbuf1.ptr()
 8         // wbuf is empty at this point.
 9     } else if wbuf.nobj == len(wbuf.obj) {
10         w.wbuf1, w.wbuf2 = w.wbuf2, w.wbuf1
11         wbuf = w.wbuf1.ptr()
12         if wbuf.nobj == len(wbuf.obj) {
13             putfull(wbuf)
14             wbuf = getempty()
15             w.wbuf1 = wbufptrOf(wbuf)
16             flushed = true
17         }
18     }
19 
20     wbuf.obj[wbuf.nobj] = obj
21     wbuf.nobj++
22 }
View Code

  get。

 1 // get dequeues a pointer for the garbage collector to trace, blocking
 2 // if necessary to ensure all pointers from all queues and caches have
 3 // been retrieved.  get returns 0 if there are no pointers remaining.
 4 //go:nowritebarrier
 5 func (w *gcWork) get() uintptr {
 6     wbuf := w.wbuf1.ptr()
 7     if wbuf == nil {
 8         w.init()
 9         wbuf = w.wbuf1.ptr()
10         // wbuf is empty at this point.
11     }
12     if wbuf.nobj == 0 {
13         w.wbuf1, w.wbuf2 = w.wbuf2, w.wbuf1
14         wbuf = w.wbuf1.ptr()
15         if wbuf.nobj == 0 {
16             owbuf := wbuf
17             wbuf = getfull()
18             if wbuf == nil {
19                 return 0
20             }
21             putempty(owbuf)
22             w.wbuf1 = wbufptrOf(wbuf)
23         }
24     }
25 
26     // TODO: This might be a good place to add prefetch code
27 
28     wbuf.nobj--
29     return wbuf.obj[wbuf.nobj]
30 }
View Code

  2. forcegc
  我们上面讲了两种 GC 触发方式:自动检测和用户主动调用。除此之后 Golang 本身还会对运行状态进行监控,如果超过两分钟没有 GC,则触发 GC。监控函数是 sysmon(),在主 goroutine 中启动。

 1 // The main goroutine
 2 func main() {
 3     ...
 4     systemstack(func() {
 5           newm(sysmon, nil)
 6     })
 7 }
 8 // Always runs without a P, so write barriers are not allowed.
 9 func sysmon() {
10     ...
11     for {
12         now := nanotime()
13         unixnow := unixnanotime()
14           
15         lastgc := int64(atomic.Load64(&memstats.last_gc))
16         if gcphase == _GCoff && lastgc != 0 && unixnow-lastgc > forcegcperiod && atomic.Load(&forcegc.idle) != 0 {
17             lock(&forcegc.lock)
18             forcegc.idle = 0
19             forcegc.g.schedlink = 0
20             injectglist(forcegc.g)    // 将 forcegc goroutine 加入 runnable queue
21             unlock(&forcegc.lock)
22         }
23     }
24 }
25 
26 var forcegcperiod int64 = 2 * 60 *1e9    //两分钟
View Code

 

转载于:https://www.cnblogs.com/hezhixiong/p/9577199.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/413409.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【一步一步学习spring】spring入门

1. spring概述 spring是一个开源框架spring为简化企业级应用开发而生&#xff0c;解决的是业务逻辑层和其他各层的松耦合问题&#xff0c;他将面向接口的编程思想贯穿整个系统应用。spring是javaSE/EE的一站式框架。web层有spring-mvc&#xff0c;业务层有spring ioc、事务等机…

JavaScript 操作 HTML DOM (文档对象模型) 相关知识点

HTML DOM 树 通过可编程的对象模型&#xff0c;JavaScript 获得了足够的能力来创建动态的 HTML。 JavaScript 能够改变页面中的所有 HTML 元素JavaScript 能够改变页面中的所有 HTML 属性JavaScript 能够改变页面中的所有 CSS 样式JavaScript 能够对页面中的所有事件做出反应…

JS运行三部曲

语法分析 预编译 解释执行 下面两句话能解决问题&#xff0c;但解决不了深入的问题&#xff0c;其实原理是 预编译产生的两个现象&#xff08;规律&#xff09;。 函数声明整体提升变量 声明提升 预编译前奏 imply global 暗示全局变量&#xff1a;即任何变量&#xff…

作用域及上下文理解

书本中的解释 [[scope]]:每个javascript函数都是一个对象&#xff0c;对象中有些属性我们可以访问&#xff0c;但有些不可以&#xff0c;这些属性仅供javascript引擎存取&#xff0c;[[scope]]就是其中一个。[[scope]]:指的就是我们所说的作用域&#xff0c;其中存储了运行期上…

windows下安装ElasticSearch的Head插件

es5以上版本安装head需要安装node和grunt(之前的直接用plugin命令即可安装) (一)从地址&#xff1a;https://nodejs.org/en/download/ 下载相应系统的msi&#xff0c;双击安装。 &#xff08;二&#xff09;安装完成用cmd进入安装目录执行 node -v可查看版本号 &#xff08;三&…

JavaScript 闭包

闭包概念&#xff1a; 当内部函数被保存到外部时&#xff0c;将会生成闭包。闭包会导致原有作用域链不释放&#xff0c;造成内存泄漏。 什么时候才会触发闭包呢&#xff1f; 当两个函数互相嵌套&#xff0c;把里面的函数被保存到了外部&#xff08;全局&#xff09;&#xff…

PyAutoIt 安装(Windows 版)

转载于:https://www.cnblogs.com/Crixus3714/p/9592635.html

scp命令:服务器间远程复制代码

scp是secure copy的简写&#xff0c;用于在Linux下进行远程拷贝文件的命令&#xff0c;和它类似的命令有cp&#xff0c;不过cp只是在本机进行拷贝不能跨服务器&#xff0c;而且scp传输是加密的。可能会稍微影响一下速度。当你服务器硬盘变为只读 read only system时&#xff0c…

Centos7安装部署Zabbix3.4

1.关闭selinux和firewall 1.1检测selinux是否关闭 [rootlocalhost ~]# getenforce Disabled #Disabled 为关闭 1.1.1临时关闭[rootlocalhost ~]# setenforce 0 #设置SELinux 成为enforcing模式1.1.2永久关闭 [rootlocalhost ~]# vi /etc…

JavaScript中的回调函数(callback)

前言 callback&#xff0c;大家都知道是回调函数的意思。如果让你举些callback的例子&#xff0c;我相信你可以举出一堆。但callback的概念你知道吗&#xff1f;你自己在实际应用中能不能合理利用回调实现功能&#xff1f; 我们在平时的学习中容易犯不去深究的病&#xff0c;功…

原型 原型链 call / apply

原型定义&#xff1a; 原型是function对象的一个属性&#xff0c;它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象&#xff0c;可以继承原型的属性和方法。原型也是对象。 利用原型特点和概念&#xff0c;可以提取共有属性。对象如何查看原型 ——> 隐…

Open-Falcon 监控系统监控 MySQL/Redis/MongoDB 状态监控

背景&#xff1a; Open-Falcon 是小米运维部开源的一款互联网企业级监控系统解决方案&#xff0c;具体的安装和使用说明请见官网&#xff1a;http://open-falcon.org/&#xff0c;是一款比较全的监控。而且提供各种API&#xff0c;只需要把数据按照规定给出就能出图&#xff0c…

最详细的后缀数组

写在前面&#xff1a; 多余的我就不提了&#xff0c;只是觉得网上的博客吧流程&#xff0c;每个数组存的是下标还是值&#xff0c;都讲的不是很清楚&#xff08;让我这种蒟蒻很是困扰&#xff09; 相信到现在这种水平的都可以知道什么是倍增&#xff0c;为什么能倍增都比较清楚…

HTML5 Web 存储(localStorage和sessionStorage)

localStorage生命周期是永久&#xff0c;除非主动清除localStorage信息&#xff0c;否则这些信息将永远存在。存放数据大小为一般为5MB,而且它仅在客户端&#xff08;即浏览器&#xff09;中保存&#xff0c;不参与和服务器的通信。 // 1、保存数据到本地// 第一个参数是保存的…

面向对象之反射、包装、(定制)

什么是反射&#xff1f; 反射的概念是由Smith在1982年首次提出的&#xff0c;主要是指程序可以访问、检测和修改它本身状态或行为的一种能力&#xff08;自省&#xff09;&#xff0c; 这一概念的提出很快引发了计算机科学领域关于应用反射的研究。它首次被程序语言的设计领域所…

Error: Cannot find module 'webpack-cli'--解决方案

npm install webpack-cli -g 全局安装解决 今日赠语&#xff1a; 哈佛大学研究心理学表示&#xff1a; 1、床乱糟糟的人&#xff0c;比穿整洁的人&#xff0c;创造力平均要高出50% 2、经常迟到的人&#xff0c;比不迟到的人&#xff0c;幽默感平均要高出70% 3、饭量大的人&…

分享菜单效果

分享菜单效果&#xff1a; 1 <!DOCTYPE html>2 <html lang"en">3 <head>4 <meta charset"UTF-8">5 <title>分享菜单</title>6 <style>7 #div1{width: 100px; height: …

vue axios解决post传参数问题

我相信遇到这个问题的兄弟们&#xff0c;不带参数的情况下都是没有问题吧&#xff0c; 如果有问题&#xff0c;百度吧&#xff0c;好解决&#xff0c;答案都比较靠谱 这里主要针对带参数的情况&#xff0c;坑多 另外&#xff0c;我默认你用postman带参测试接口是没问题的 不…

Spring Boot实践——基础和常用配置

借鉴&#xff1a;https://blog.csdn.net/j903829182/article/details/74906948 一、Spring Boot 启动注解说明 SpringBootApplication开启了Spring的组件扫描和Spring Boot的自动配置功能。实际上&#xff0c; SpringBootApplication将三个有用的注解组合在了一起。 Spring的Co…

[css] 什么是hack?css的hack有哪些?

[css] 什么是hack&#xff1f;css的hack有哪些&#xff1f; 一、总结 1、CSS hack&#xff1a;由于不同厂商的浏览器&#xff0c;比如Internet Explorer,Safari,Mozilla Firefox,Chrome等&#xff0c;或者是同一厂商的浏览器的不同版本&#xff0c;如IE6和IE7&#xff0c;对CS…