細談Go變量的內存分佈

咱們程序中的變量大多被分配在內存的兩個區域:statckheap程序員

stackheap

首先讓咱們一塊兒來回顧一下進程的內存分配: 咱們寫的程序代碼跑起來後,會是一個進程;OS會給咱們的進程分配內存;內存結構大體以下: 在這裏插入圖片描述編程

OS給一個進程分配的內存空間大體能夠分爲:代碼區全局數據區棧(stack)堆(heap)環境變量區域以及中間空白的緩衝區六個部分。其中,數據的增加路徑除棧(stack)是由高到低以外,其他的均是由低到高(可看圖中數據箭頭)。微信

咱們思考一下,爲何棧(stack)區這麼特殊和其餘區域路徑相反?還有,進程內存中stackheap和數據結構中的stackheap名字都相同,是有什麼聯繫嗎?請帶着問題往下看:markdown

進程內存中的stackheap

stack : 是由程序側經過系統調用向操做系統申請的,由操做系統管理和釋放,不須要程序員手動管理;通常用於存放線程和函數中產生的臨時變量。這塊區域的數據使用速度較快,不用手動管理,省心省力。數據結構

heap:是由程序側經過系統調用向操做系統申請的,可是須要程序員自行管理的內存區域,由於此區域的定位是global variable,用於存放全局的變量(雖然不少編程語言中不這麼利用); 程序員須要手動或者經過GC及時free或者delete此內存區域中的數據,可是也要注意:若是頻繁的進行刪除和添加,會致使內存碎片。編程語言

數據結構stackheap

咱們再來看看數據結構中的stackheap函數

stack工具

在這裏插入圖片描述

後進先出LIFO的數據結構。性能

Heapui

在這裏插入圖片描述

堆的定義:

  • 徹底二叉樹
  • 每個節點的值都必須大於等於(或小於等於)其子樹中每一個節點的值

根節點是最大數的叫作「大頂堆」,根節點是最小數的叫作「小頂堆」。

heap這種數據結構常常利用在「如何快速定位並獲取到Top N最熱門的xxx」,一般的作法以下圖:

在這裏插入圖片描述

內存中的"stackheap"與數據結構中的"stackheap"的聯繫

一句話總結:進程內存中的棧區(stack)使用的數據結構就是stack,內存中的heap和數據結構中的heap則毫無關係。

看以下C代碼:

int main() {
   int a = 3; 
   int b = 5;
   ret = add(a, b);
   printf("%d", ret);
   reuturn 0;
}

int add(int x, int y) {
   int sum = 0;
   sum = x + y;
   return sum;
}
複製代碼

以上代碼在棧區中的數據是這樣的:

在這裏插入圖片描述

還及得上文中提到的:「進程內存中只有棧區(stack)數據是由高位向低位增加的,其他的均爲由低位向高位增加嗎?」

棧區用的數據結構是棧,函數變量的銷燬和返回順序用逆剛好符合stack先進後出的特色,我以爲這是棧區(stack)逆序增加很重要的一點。

可是,最根本的緣由仍是在於:歷史遺留問題。請看下圖:

在這裏插入圖片描述 在當初那個內存空間及其短缺的年代,你認爲左圖仍是右圖更適合「緩衝區」?由於stack區域和heap區域大小都是動態分配的,都有「不肯定性」,很顯然,左圖發生堆棧重疊更小,且更適合內存的充分利用。

Go變量的位置

咱們在寫C、PHP、Java的時候,能夠很容易的知道,所寫的變量所在的位置:帶newmalloc等字段的,那必定是在堆上分配了,至於後續GC怎麼處理,有沒有引用繼續關聯,堆有沒與釋放,程序是否存在內存泄露...這都是後續處理的問題了;變量的存儲位置是妥妥的堆上了。可是,在用Go的時候要注意,newmake等等關鍵字都很差使,Go變量的位置不是由寫程序的程序員來決定的,而是Go自行處理;因此可能你的變量是new出來的,可是,最終也不必定分配到堆上,極可能是分配在棧上。

Go把變量的位置在哪兒這件事對程序員「隱藏」了,Go自行處理;由於Go認爲:變量的存儲位置,會對程序的性能有必定影響,而Go是計劃打造對性能有極致要求的程序,於是本身管了。 Go是這麼管的: 首先,棧stack上的效率確定是比堆要高的,這算是常識;Go在編譯期會對每個函數變量作判斷,若是不可以判斷此函數中的變量在返回以後是否仍被引用到,就給把變量扔堆heap上,不然,就扔棧stack上。可是注意:若是變量很是大,仍是會扔到堆heap上。

逃逸分析

咱們是否有辦法知道咱們寫的Go程序中變量的位置呢? 答案是有的,Go向開發者提供了變量逃逸分析的工具

go build -gcflags '-m -l' main.go // 這裏的main.go也能夠是某個具體的二進制應用程序
複製代碼

下面對以下代碼進行逃逸分析:

import (
    "fmt"
)

func main(){
    a:= 3
    b := 5
    ret := add(a, b)
    fmt.Println(ret)
}

func add(x,y int)int {
    sum := x + y
    return sum
}
複製代碼

分析結果:

./main.go:11:16: main ... argument does not escape
./main.go:11:16: ret escapes to heap

複製代碼

更多精彩內容,請關注個人微信公衆號 互聯網技術窩

相關文章
相關標籤/搜索