一、Linux系统内存

在说明golang内存分配之前,先了解下Linux系统内存相关的基础知识,有助于理解golang内存分配原理。

1.1 虚拟内存技术

在早期内存管理中,如果程序太大,超过了空闲内存容量,就没有办法把全部程序装入到内存,这时怎么办? 在许多年前,人们采用了一种叫做覆盖技术,这样一种解决方案。

这是一种什么样的解决方案?

就是把程序分为若干个部分,称为覆盖块(overlay),核心思想就是分解(跟现代架构技术中分解、分模块思想很相近)。然后只把那些需要用到的指令和数据保存在内存中,而把其余的指令和数据保存在内存外。关键是需要程序员手动来分块。

这种技术有什么问题呢?

这种技术必须由程序员手工把一个大的程序划分为若干个小的功能模块,并确定各个模块之间的调用关系。手工做这种事情很费时费力,使得编程复杂度增加。但是,程序员总是爱“偷懒”的,于是,人们去寻找更好的方案。

这个方案就是虚拟内存技术,它的基本思路:

程序运行进程的总大小可以超过实际可用的物理内存的大小。每个进程都可以有自己独立的虚拟地址空间。然后通过CPU和MMU把虚拟内存地址转换为实际物理地址。

这个就相当于在物理内存和程序之间增加了一个中间层,虚拟内存。

虚拟存储也可以看作是对内存的一种抽象。而且这种抽象带来诸多好处:

  1. 它将内存看成是一个存储在磁盘上的地址空间的高速缓存,在内存中只保留了活动区域,可以根据需要在磁盘和内存间来回传送数据,高效使用内存。
  2. 它为每个进程提供了一致的地址空间,简化了存储的管理。
  3. 对进程起到保护作用,不被其他进程地址空间破坏,因为每个进程的地址空间都是相互独立。

(程序:静态的程序;进程:动态的,可以看作是程序的一个实例)

坏处:就是复杂度进一步增加,这也是必然的。不过相比带来的好处,复杂度的增加还是可以接受,并克服。

Linux中对进程的处理抽象成了一个结构体 task_struct,我前面文章有对这个结构体的介绍。下面就看看进程的内存。

1.2 进程的内存

进程内存在linux(32位)中的布局:

来自:https://manybutfinite.com/post/anatomy-of-a-program-in-memory/

最高位的1GB是linux内核空间,用户代码不能写,否则触发段错误。下面的3GB是进程使用的内存。

Kernel space:linux内核空间内存

Stack:进程栈空间,程序运行时使用。它向下增长,系统自动管理

Memory Mapping Segment:内存映射区,通过mmap系统调用,将文件映射到进程的地址空间,或者匿名映射。

Heap:堆空间。这个就是程序里动态分配的空间。linux下使用malloc调用扩展(用brk/sbrk扩展内存空间),free函数释放(也就是缩减内存空间)

BSS段:包含未初始化的静态变量和全局变量

Data段:代码里已初始化的静态变量、全局变量

Text段:代码段,进程的可执行文件

二、内存管理中的一些常见问题

1、未能释放已经不再使用的内存 - 内存泄漏

2、指向不可用的内存指针 - 野指针

3、指针所指向的对象已经被回收了,但是指向该对象的指针仍旧指向已经回收的内存地址 - 悬挂指针

4、分配或释放内存太快或者太慢

5、分配内存大小不合理,造成内存碎片问题

6、内存碎片问题

三、TCMalloc

可以查看前面的文章 TCMalloc内存分配简析,TCMalloc内存分配器的原理和golang内存分配器原理相近,所以理解了TCMalloc,golang内存分配原理也就理解大半,不过golang对它也有一些改动。

四、golang内存

4.1 golang怎么解决常见内存问题

golang是怎么解决 的内存管理中的常见问题的呢?

针对上面的1、2、3 这三种问题,golang使用自动垃圾回收机制,一般情况下,都不使用指针运算(要运算用unsafe包),很少的指针使用。当然,内存泄漏问题不能完全根除,但是可以解决一大部分问题。

针对下面的4、5、6 这三种问题,golang采用了多级缓存,预分配的方法,来加快内存分配和释放回收,尽量减少内存碎片。详见 TCMalloc内存分配简析

4.2 为什么要重新写一个内存分配器

内核已经有一个malloc的内存分配器,为什么还有重写一个内存分配器?

可以看到,malloc是一个很悠久的内存分配器,但是随着时代的发展,多核多线程已经普及,为了更好的应用多线程,提高程序效率,以及改进内存碎片,所以重新写了一个内存分配器。从这里 TCMalloc内存分配简析 可以看出TCMaloc的优点,它将内存划分为多级别,减少锁的开销。而且每个线程的缓存又分开了多个小的对象,以减少内存碎片。等等优化改进。

所以go内存分配也继承了这些优点。go还有一个原因,那就是go还有GC,需要配合内存的垃圾回收。

4.3 内存管理到底管理哪个区域

从上面的进程内存布局图,可以看出一个进程的内存划分了好多不同的区域,而内存管理主要管理的就是Stack和Heap,其中Stack (栈)区主要由编译器和系统管理,程序语言主要管理Heap(堆)。而且这里的进程内存指的是虚拟内存。

4.4 golang内存中的概念

golang内存分配的基本思想来自TCMalloc,所以go内存分配中的几个概念与TCMalloc很相似,可以看看TCMalloc 中的概念

mspan

mspan跟tcmalloc中的span相似,它是golang内存管理中的基本单位,也是由页组成的,每个页大小为8KB,与tcmalloc中span组成的默认基本内存单位页大小相同。mspan里面按照8*2n大小(8b,16b,32b .... ),每一个mspan又分为多个object。

就连名字也很像,mspan中的m应该是memory的第一个字母。

mcache

mcache跟tcmalloc中的ThreadCache相似,ThreadCache为每个线程的cache,同理,mcache可以为golang中每个Processor提供内存cache使用,每一个mcache的组成单位也是mspan。

mcentral

mcentral跟tcmalloc中的CentralCache相似,当mcache中空间不够用,可以向mcentral申请内存。可以理解为mcentral为mcache的一个“缓存库”,供mcaceh使用。它的内存组成单位也是mspan。

mcentral里有两个双向链表,一个链表表示还有空闲的mspan待分配,一个表示链表里的mspan都被分配了。

mheap

mheap跟tcmalloc中的PageHeap相似,负责大内存的分配。当mcentral内存不够时,可以向mheap申请。那mheap没有内存资源呢?跟tcmalloc一样,向OS操作系统申请。

还有,大于32KB的内存,也是直接向mheap申请。

总结

golang内存分配几个相关概念,用图来总结一下:

后面再进一步分析golang的内存分配信息。

五、参考

深入理解golang:内存分配原理的更多相关文章

  1. TCMalloc 内存分配原理简析

    一.TCMalloc TCMalloc简介 为啥要介绍 TCMalloc? 因为golang的内存分配算法绝大部分都是来自 TCMalloc,golang只改动了其中的一小部分.所以要理解golang ...

  2. 图解golang内存分配机制 (转)

    一般程序的内存分配 在讲Golang的内存分配之前,让我们先来看看一般程序的内存分布情况: 以上是程序内存的逻辑分类情况. 我们再来看看一般程序的内存的真实(真实逻辑)图: Go的内存分配核心思想 G ...

  3. 深入Java核心 Java内存分配原理精讲

    深入Java核心 Java内存分配原理精讲 栈.堆.常量池虽同属Java内存分配时操作的区域,但其适用范围和功用却大不相同.本文将深入Java核心,详细讲解Java内存分配方面的知识. Java内存分 ...

  4. 深入理解Linux内存分配

    深入理解Linux内存分配 为了写一个用户层程序,你也许会声明一个全局变量,这个全局变量可能是一个int类型也可能是一个数组,而声明之后你有可能会先初始化它,也有可能放在之后用到它的时候再初始化.除此 ...

  5. JVM内存分配原理

    堆栈常量池等内存分配原理详解 存储的方式: 寄存器 栈(stack) 堆(heap) 静态域 常量池 非RAM存储 JAVA寄存器 最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制.  ...

  6. golang内存分配

    golang内存分配 new一个对象的时候,入口函数是malloc.go中的newobject函数 func newobject(typ *_type) unsafe.Pointer { flags ...

  7. 深入理解JVM内存分配策略

    理解JVM内存分配策略 三大原则+担保机制 JVM分配内存机制有三大原则和担保机制 具体如下所示: 优先分配到eden区 大对象,直接进入到老年代 长期存活的对象分配到老年代 空间分配担保 对象优先在 ...

  8. Java内存区域划分、内存分配原理(深入理解JVM一)

    Java虚拟机在执行Java的过程中会把管理的内存划分为若干个不同的数据区域.这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,而有的区域则依赖线程的启动和结束而创建和销 ...

  9. linux环境内存分配原理 mallocinfo

    Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全 ...

随机推荐

  1. 搭建 Spring 源码阅读环境

    前言 有一个Spring源码阅读环境是学习Spring的基础.笔者借鉴了网上很多搭建环境的方法,也尝试了很多,接下来总结两种个人认为比较简便实用的方法.读者可根据自己的需要自行选择. 方法一:搭建基础 ...

  2. Centos-文本过滤-grep

    grep 文本过滤,对文本的每一行进行关键字搜索,如果找到则输出 相关选项 -A 除了列出符合关键字的行外,还输出符合关键字行后多少行内容 -c 只显示符号条件的行号 -f  批量搜索,把关键字写入到 ...

  3. Python下的图像处理库,你选哪个?

    奥里给~ 转载:https://blog.csdn.net/chen801090/article/details/105795068/ 在进行数字图像处理时,我们经常需要对图像进行读取.保存.缩放.裁 ...

  4. 【学习笔记】Polya定理

    笔者经多番周折终于看懂了\(\text{Burnside}\)定理和\(\text{Polya}\)定理,特来写一篇学习笔记来记录一下. 群定义 定义:群\((G,·)\)是一个集合与一个运算·所定义 ...

  5. 关于freemodbus协议中eMBFuncReadHoldingRegister()函数的所谓错误

    摘要:网上看到有好心的网友提示,freemodbus协议中的mbfuncholding.c 文件中eMBFuncReadHoldingRegister()函数,有一处错误,即:第185行的" ...

  6. 发现3 .js与Android和英特尔XDK

    下载example3.zip - 456.5 KB 下载apk14.zip - 6.8 MB 下载apk13.zip - 6.8 MB Introduction  本文是关于使用Intel XDK和t ...

  7. ASP。使用依赖注入的asp.net Core 2.0用户角色库动态菜单管理

    下载source code - 2.2 MB 介绍 在开始这篇文章之前,请阅读我的前一篇文章: 开始使用ASP.NET Core 2.0身份和角色管理 在上一篇文章中,我们详细讨论了如何使用ASP.N ...

  8. JVM 第三篇:Java 类加载机制

    本文内容过于硬核,建议有 Java 相关经验人士阅读. 1. 什么是类的加载? 类的加载指的是将类的 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 ...

  9. 用Python爬取B站、腾讯视频、爱奇艺和芒果TV视频弹幕!

    众所周知,弹幕,即在网络上观看视频时弹出的评论性字幕.不知道大家看视频的时候会不会点开弹幕,于我而言,弹幕是视频内容的良好补充,是一个组织良好的评论序列.通过分析弹幕,我们可以快速洞察广大观众对于视频 ...

  10. Linux 下 svn 场景实例及常用命令详解

    一.SVN使用场景实例 问题: 在使用svn做为版本控制系统的软件开发中,经常会有这样的需求:在工作复本目录树的不同目录中增加了很多文件,但未纳入版本控制系统,这时如果使用svn add命令一个一个的 ...