细谈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

复制代码

更多精彩内容,请关注个人微信公众号 互联网技术窝

相关文章
相关标签/搜索