众所周知,计算机设计者为了硬件上实现起来比较简单。

因此,他们比较偷懒的让计算机用二进制进行表达数值。

用一个电子开关,断开时,表示 0  接通时表示 1

一个开关表示一个位 bit (比特),只能表示0和1,能表达的数量太少。

所以我们一般把8个开关合起来使用,8个bit表示一个字节byte, 这样组合起来就有256个状态,就可以表达 0- 255啦。


是不是很妙呀,但不幸的是电信运营商经常用这一点来忽悠客户,什么1000M网速,你会发现你永远达不到每秒下一个1000M的文件,因为我们以为他的单位是1000M byte,实际单位是 1000M bit ,tnnd

内存其实可以简单的理解为有无数的开关,1个G的内存,就相当于有

1G * 1024 = 1024M

1024M * 1024 = 1,048,576 kb

1048576 kb * 1024 =  1,073,741,824 byte

1,073,741,824 byte * 8 = 8,589,934,592 bit  


大约80多亿个开关。

听起来挺多,其实非常不经用。

比如一张高清图 20M, 1024 / 24 约等于 42,就是40多张图这样大小的图,就把一个G内存撑爆了。撑爆了就死机了,就得崩。


上面我们看到了,内存就是一堆开关,用来保存信息。

栈内存也好,堆内存也罢,都是开关,甚至开关的型号都是完全一模一样的。

那为啥要取不同的名字呢?主要是我们对内存可以在逻辑上进行区分,有不同的使用方法,使用方法不同,表现主来的效果不一样,为了好区分。

内存地址:第一步给内存编号,这个应该好理解,上亿的开关,还是一模一样,不给个编号,咋用呀?这个内存编号,就可以理解为内存地址。 使用的时候,就说把第n号开关关上或者打开。


内存区域:有了内存地址之后,还要对内存进行分区域。 就像房屋空间一样,虽然都是空间,但分成客厅、卧室、卫生间等不同的区域,会让使用更合理。 大体上内存按用途进行划分:内核区,主要是操作系统用;进程区,主要就是我们写的程序用啦。


进程区:我们作为golang开发者,内核区不用去操心,主要看看进程区,他可以再细分为代码区、全局区、堆区、栈区。这几个区里面,我们重点关心关心堆区和栈区。

栈内存区:为啥叫栈内存区呢?是因为我们按照栈数据结构来使用这个内存区域。栈数据结构,简单的来说,就是数据重叠起起来,就像把很多书一本本叠起来一样,最先放的压在最下面,最后放的放在最上面。取的时候刚好相反,最后放的先取到,最先放的后取到。

至于为啥要这样设计呢,因为就是这样简单的出栈与入栈就能实现程序的运行,感觉趣的朋友可以自行深入研究栈的原理及应用。栈内存区一般不会太大,系统是有限制的,一般最大也就几百m。而且因为他是连续的内存区,使用效率上比较高。

堆内存区: 堆嘛,特点就是比较大,理论上没有上限,只有你买的内存的物理大小会限制它的发挥。然后他在使用上是不连续的,所以一般会用链表记录使用情况。所以使用效率上会比栈低一些,也就是慢一些。



栈比较快,一般来说局部变量会放到栈内存区。而且退出函数后,入栈的数据会全部出栈。占用的栈就清空,可以继续使用了。不会造成内存的过多占用,由于是连续区域,所以也不会造成过多的内存碎片化。

每个协程会维护一个自已独有的栈。其它协程是不能访问的,所在协程内对栈上数据的访问不用考虑数据同步。


堆内存比较大,所以比较大的数据一般放在堆上。 堆内存不会像栈内存一样出栈就自动释放了,所以全局数据如包变量,也会放在堆内存上面。缺点就是那些数据占用的堆区域如果不再使用,需要由gc来判断是否释放。内存的释放不是那么的及时,而且判断那些内存可以释放,也会消耗算力,影响程序的性能。内存的释放容易造成内存碎片,不集中,也会影响数据读写的效率。不过,golang的gc也在不断的提升算法,所以现在看影响并不大。

每个程序只有一个堆,意思就是多个协程共享一个堆,所有多个协程可能会同时访问同一个数据,所以在需要的时候,要考虑数据同步的问题。

golang编译器会根据一定标准,如逃逸分析等,自主决定把数据放在栈上或者是堆上。就是程序员可以偷个懒,不用自行去处理内存分配与释放等操作,比如安全。