go 語言 runtime-debug 包詳解

【導讀】本文對 runtime/debug 包內的方法進行了詳細介紹。

程序包調試了包含程序在運行時進行調試功能, 就針對 api 進行一一講解

1. 強制進行垃圾回收

func FreeOSMemory()

FreeOSMemory 強制進行一次垃圾收集,以釋放盡量多的內存回操作系統。(即使沒有調用,運行時環境也會在後臺任務裏逐漸將內存釋放給系統)

package main

import (
  "runtime"
  "fmt"
    "time"
)

func main() {
  var dic = new(map[string]string)
  runtime.SetFinalizer(dic, func(dic *map[string]string) {
    fmt.Println("內存回收")
  })
  time.Sleep(time.Second)
}

執行執行完畢, dic 對象沒有被執行回收操作, 下面我們調用這個方法, runtime.SetFinalizer 對象內存釋放觸發這個方法

package main

import (
  "runtime"
  "fmt"
    "time"
  "runtime/debug"
)

func main() {
  var dic = new(map[string]string)
  runtime.SetFinalizer(dic, func(dic *map[string]string) {
    fmt.Println("內存回收")
  })
  debug.FreeOSMemory()
  time.Sleep(time.Second)
}

2. 將堆棧蹤跡打印到標準錯誤

func SetGCPercent(percent int) int

SetGCPercent 設定垃圾收集的目標百分比:當新申請的內存大小佔前次垃圾收集剩餘可用內存大小的比率達到設定值時,就會觸發垃圾收集。SetGCPercent 返回之前的設定。初始值設定爲環境變量 GOGC 的值;如果沒有設置該環境變量,初始值爲 100。percent 參數如果是負數值,會關閉垃圾收集

package main

import (
  "runtime"
  "fmt"
    "time"
  "runtime/debug"
)

func main() {
  fmt.Println(debug.SetGCPercent(1))

  // 1
  var dic = make([]byte,100,100)
  runtime.SetFinalizer(&dic, func(dic *[]byte) {
    fmt.Println("內存回收1")
  })

  // 立即回收
  runtime.GC()

  // 2
  var s = make([]byte,100,100)
  runtime.SetFinalizer(&s, func(dic *[]byte) {
    fmt.Println("內存回收2")
  })

  // 3
  d := make([]byte,300,300)
  for index,_ := range d {
    d[index] = 'a'
  }
  fmt.Println(d)

  time.Sleep(time.Second)
}

1 處我們創建了一塊內存空間 100 字節,只有我們調用了runtime.GC()立即回收了內存,2 處我們又創建了一塊 100 字節的內存,等待回收,當我們執行到 3 處的時候,創建了一個 300 字節的內存, 已大於垃圾回收剩餘內存, 所以系統繼續立即回收內存。

3. 設置被單個 go 協程調用棧可使用的內存最大值

func SetMaxStack(bytes int) int

import (
    "fmt"
    "time"
  )

func main() {
  for i:=0;i < 1000;i++{
    go print()
  }
  time.Sleep(time.Second)
}
func print(){
  fmt.Println("1")
}

我們在 main 函數中使用 for 循環啓用了 1000 個 go 協程,下面是正常的輸出

接下來我們來限制一下棧的內存

package main
import (
    "fmt"
    "time"
    "runtime/debug"
)

func main() {
  debug.SetMaxStack(1)
  for i:=0;i < 1000;i++{
    go print()
  }
  time.Sleep(time.Second)
}
func print(){
  fmt.Println("1")
}

fmt.Println(debug.SetMaxStack(1)) 查看到默認系統爲 1000 000 000 字節

系統報了一個棧溢出的錯誤, 這個方法的主要作用是限制無限遞歸 go 成帶來的災難,默認的設置 32 位系統是 250MB,64 位爲 1GB

4. 設置 go 程序可以使用的最大操作系統線程數

func SetMaxThreads(threads int) int

import (
    "fmt"
    "time"
    "runtime/debug"
  )

func main() {
  debug.SetMaxThreads(1)
  go print()
  time.Sleep(time.Second)
}

func print(){
  fmt.Println("1")
}

我們把程序的組大可使用的線程(不是協程)數設置爲 1,如果程序試圖超過這個限制, 程序就會崩潰,初始設置爲 10000 個線程
什麼時候會創建新的線程呢?
現有的線程阻塞,cgo 或者 runtime.LockOSThread 函數阻塞其他 go 協程

5. 設置程序請求運行是隻觸發 panic, 而不崩潰

func SetPanicOnFault(enabled bool) bool

SetPanicOnFault 控制程序在不期望(非 nil)的地址出錯時的運行時行爲。這些錯誤一般是因爲運行時內存破壞的 bug 引起的,因此默認反應是使程序崩潰。使用內存映射的文件或進行內存的不安全操作的程序可能會在非 nil 的地址出現錯誤;SetPanicOnFault 允許這些程序請求運行時只觸發一個 panic,而不是崩潰。SetPanicOnFault 只用於當前的 go 程

package main

import (
    "fmt"
    "time"
    "runtime/debug"
)

func main() {

  go print()
  time.Sleep(time.Second)

  fmt.Println("ddd")
}

func print(){
  defer func() {recover()}()
  fmt.Println(debug.SetPanicOnFault(true))
  var s *int = nil
  *s = 34
}

我們發現指針爲 nil 發生了 panic 但是我們進行了恢復, 程序繼續執行

6. 垃圾收集信息的寫入 stats 中

func ReadGCStats(stats *GCStats)
我們看一下 CGStats 的結構

type GCStats struct {
    LastGC         time.Time       // 最近一次垃圾收集的時間
    NumGC          int64           // 垃圾收集的次數
    PauseTotal     time.Duration   // 所有暫停收集垃圾消耗的總時間
    Pause          []time.Duration // 每次暫停收集垃圾的消耗的時間
    PauseQuantiles []time.Duration
}

我們寫一個示例演示一下用法

package main

import (
    "fmt"
    "runtime/debug"
    "runtime"
)

func main() {
  data := make([]byte,1000,1000)
  println(data)
  runtime.GC()

  var stats debug.GCStats
  debug.ReadGCStats(&stats)
  fmt.Println(stats.NumGC)
  fmt.Println(stats.LastGC)
  fmt.Println(stats.Pause)
  fmt.Println(stats.PauseTotal)
  fmt.Println(stats.PauseEnd)
}

7. 將內存分配堆和其中對象的描述寫入文件中

func WriteHeapDump(fd uintptr)
WriteHeapDump 將內存分配堆和其中對象的描述寫入給定文件描述符 fd 指定的文件。
堆轉儲格式參見 http://golang.org/s/go13heapdump

package main

import (
   "runtime/debug"
   "runtime"
   "os"
)

func main() {
  fd,_ := os.OpenFile("/Users/xujie/go/src/awesomeProject/main/log.txt",os.O_RDWR|os.O_CREATE,0666)
  debug.WriteHeapDump(fd.Fd())
  data := make([]byte,10,10)
  println(data)
  runtime.GC()
}

8. 獲取 go 協程調用棧蹤跡

func Stack() []byte
Stack 返回格式化的 go 程的調用棧蹤跡。對於每一個調用棧,它包括原文件的行信息和 PC 值;對 go 函數還會嘗試獲取調用該函數的函數或方法,及調用所在行的文本

package main

import (
  "fmt"
  "runtime/debug"
  "time"
)

func main() {
 go print()
 time.Sleep(time.Second)
}

func print(){
  fmt.Println(string(debug.Stack()))
}

我們可以使用 runtime 包中的方法查看更相信的內容

package main

import (
   "time"
  "runtime"
  "fmt"
)

func main() {
 go print()
 time.Sleep(time.Second)
}

func print(){
  callers := make([]uintptr,100)
  n:=runtime.Callers(1,callers)
  for _,pc:= range callers[:n]{
    funcPc := runtime.FuncForPC(pc)
    fmt.Println(funcPc.Name())
    fmt.Println(funcPc.FileLine(pc))
  }
}

9. 將 Stack 返回信息打印到標準錯誤輸出

func PrintStack()

package main

import (
      "time"
      "runtime/debug"
)

func main() {
 go print()
 time.Sleep(time.Second)
}

func print(){
  debug.PrintStack()
}

轉自:

jianshu.com/p/0b3d11f7af57

Go 開發大全

參與維護一個非常全面的 Go 開源技術資源庫。日常分享 Go, 雲原生、k8s、Docker 和微服務方面的技術文章和行業動態。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/bwsM1q1zuDJgwBZ8JHf-TQ