golang内存分配

go语言内存分配概述

go语言的内存分配是基于tcmalloc模型的,关于tcmalloc可以搜索《图解TCMalloc》

go语言跟大多数内置运行时(runtime)的编程语言一样,抛弃传统内存分配的方式,改为自己管理。这样可以完成类似预分配、内存池等操作,以闭开系统调用带来的性能问题,防止每次分配内存都需要系统调用。

go的内存分配的核心思想分为以下几点:

  • 每次从操作系统申请一大块内存,由go来对这块内存做分配,减少系统调用。
  • 内存分配算法采用Google的TCMalloc算法。
  • 回收对象内存时,并没有将其真正地释放掉,只是放回预先分配的大块内存中,以便复用。只有内存闲置过多的时候,才会尝试归还部分内存给操作系统,整体降低开销。

go语言实现跨平台

由于各平台使用的指令集和架构不同,同样的二进制在不同平台的解释不同。

java使用适配器模式,先把代码编译成.class文件,通过java虚拟机在不同平台上进行转换

高级语言通过编译器编译成汇编语言(.s文件),汇编器再将汇编语言转换成二进制(.o文件),再通过链接器链接成可执行文件。

go语言在编译的时候,通过指定GOOS和GOARCH实现跨平台

计算机内存

字节是内存分配的最小单元,每个单元都有一个内存编号,访问某一块内存时通过内存编号去访问。那这个内存编号是怎么存储的?内存编号是通过段地址加偏移量来确定,段地址存放在段地址寄存器中。每个程序都有自己的进程地址空间,要访问某一块内存时,通过地址总线来搜寻内存。比如32位系统的总线只有32位,最多只能寻址4G.

CPU首先将需要访问的地址输出到地址总线 ,从地址总线发出行列地址给内存控制器,内存控制器寻址到指定的存储单元,从数据总线发回数据

内存是一个多级缓存,其中l0为寄存器,l1~l3高速缓存被内置在cpu芯片中。l4为主存,l5为辅存,l6为远程二级存储

局部性原理:每次cpu读取内存数据时,内存都会将局部的内存都传递上去。

内存对齐:目前计算机内存按照字节编址,每个地址的内存大小为1个字节。而读取数据的大小和数据线有关。比如数据线为8位那么一次读取一个字节,而如果数据线位32位,那么一次需要读取4个字节,这样是为了一次更多的获取数据提高效率。否则读取一个int变量就需要进行4次内存操作。

CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。块大小成为memory access granularity(粒度)。

对于内存访问一般有以下两个条件:

  1. cpu进行一次内存访问读取的数据和字长相同。比如64位一次内存访问读取8个字节
  2. 有些cpu只能对字长背书的内存地址进行访问。比如32位CPU,只能对 0,4,8,16的内存地址进行寻址

为了兼容第二个条件,如果不进行内存对齐,可能导致进行多次内存访问。

什么是内存(一):存储器层次结构
计算机原理学习(3)-- 内存工作原理

golang内存对齐

golang内存对齐规则:

  1. 结构体的成员变量,第一个成员变量的偏移量为 0。往后的每个成员变量的对齐值必须为编译器默认对齐长度(#pragma pack(n))或当前成员变量类型的长度(unsafe.Sizeof),取最小值作为当前类型的对齐值。其偏移量必须为对齐值的整数倍
  2. 结构体本身,对齐值必须为编译器默认对齐长度(#pragma pack(n))或结构体的所有成员变量类型中的最大长度,取最大数的最小整数倍作为对齐值
  3. 结合以上两点,可得知若编译器默认对齐长度(#pragma pack(n))超过结构体内成员变量的类型最大长度时,默认对齐长度是没有任何意义的

虚拟内存

1.每个进程都有各自独立的4G 字节的虚拟地址空间。4G的进程空间分为两部分,0~3G为用户空间,3G~ 4G 为内核空间。
2.用户程序中使用的都是虚拟地址空间中的地址,永远无法直接访问实际物理地址。
3.虚拟内存到物理内存的映射由操作系统动态维护。
4.虚拟内存一方面保护了操作系统的安全,另一方面允许应用程序使用比实际物理内存更大的地址空间。
5.用户空间中的代码不能直接访问内核空间中的代码和数据,但是可以通过系统调用进入内核态,间接地与内核交互。
6.对内存的越权访问,或访问未建立映射的虚拟内存(野指针、不在映射表中),将会导致段错误。
7. 用户空间对应进程,进程一切换,用户空间随即变换。
内核空间由操作系统内核使用,不会随进程切换而变化。
内核空间由内核根据独立且唯一的页表init_mm.pgd 进行映射,而用户空间的页表则每个进程一份。
8. 每个进程的内存空间完全独立,因此在不同进程之间交换虚拟地址毫无意义。
9.虚拟内存到物理内存的映射,以页(4096字节)为单位。

在缓存原理中,数据都是按块来进行逻辑划分的,一次换入/换出的数据都是以块为最小单位,这样提高了数据处理的性能。同样的原理应用到具体的内存管理时,使用了页(page)来表示块,虚拟地址空间划分为多个固定大小的虚拟页(Virtual Page, VP),物理地址空间划分为多个固定大小的物理页(Physical Page, PP), 通常虚拟页大小等于物理页大小,这样简化了虚拟页和物理页的映射。虚拟页的大小通常在4KB - 2MB之间。在JVM调优的时候有时候会使用2MB的大内存页来提高GC的性能

什么是内存(二):虚拟内存
为什么 Go 占用那么多的虚拟内存?
go语言并发讲解,虚拟内存的讲解
图解 Go 内存分配器
虚拟内存
计算机底层知识拾遗(一)理解虚拟内存机制
虚拟内存和物理内存如何映射?

Reference

图解Golang的内存分配
golang内存管理学习笔记
Golang 内存管理
golang 内存分配深度分析
Golang:内存分配和垃圾回收
golang内存分配

本节关键词

什么是内存、cpu访问内存数据、go虚拟内存、虚拟内存、物理内存和虚拟内存映射、go内存分配、内存对齐