跳至主要內容

内存管理

naijoug大约 6 分钟

“内存”顾名思义就是我们平常所说的电脑内存条。专业术语:随机存取存储器 RAM(Random Access Memory)。这个随机存取是重点,表明了存储器的特性,指定的是访问存储器的任意位置数据的时间周期都是恒定的。

reference

存储器

访问方式存储器
顺序存取(Sequential Access)这类存储器已经比较远古,磁带、黑胶唱片。
随机存取(Random Access)内存条 RAM(Random Access Memory)、磁盘存储器 HDD(Hard Disk Drive)、固态硬盘 SSD (Solid-State Drive)。

顺序存取的介质,访问数据时都必须重头开始,所以访问速度是不如随机存储的存储器的。使用过的磁带的朋友应该很好理解这个过程,每次要听指定位置的歌曲,都必须重头开始不停快进才能定位指定位置。那说完顺序存取存储器,那接下来说说随机存取存储器的问题。

大家都是随机存取(Random Access)为什么“内存”访问速度快?

这些存储的访问速度的快慢就与存储数据介质材料和制造工艺有关。

存储器制造工艺工作原理
RAM半导体通过改变电子状态来实现数据存储
HDD磁性材料通过磁头对磁盘的的磁性状态改变,来实现数据存储
SSD晶体管通过向晶体管加电,改变电荷数量来实现数据的存储

内存分区

一个程序启动时,会被操作系统分配一块内存区域。程序运行过程中,又将得到的这块内存区域分为如下五个区域。其中代码区、常量区、全局/静态区大小在程序加载之后,大小不变。栈区和堆区的内存大小会根据程序运行过程中动态调整。

分区说明
代码区存放程序的二进制代码,这块区域在程序加载到内存中之后大小不变
常量区程序定义的常量存储区域
全局/静态区存放程序定义的全局和静态变量
栈区由操作系统分配和管理,主要存储局部变量、函数参数等
堆区由程序员手动申请和释放,如果管理不当会导致内存泄漏的问题
  • 程序可执行文件结构

    类型可读写说明
    .text只读代码区
    rodata只读常量区
    .data读写全局变量和静态变量区 (已经初始化)
    .bss读写全局变量和静态变量区 (未初始化)
    heap读写堆区
    stack读写栈区
  • 全局变量 & 静态变量

    类型说明
    全局变量变量在整个项目中都能使用,不同文件不能命名相同的全局变量名
    静态变量static 对变量进行了作用域限制 (函数外: 当前文件可见 ; 函数内: 当前函数可见,多次调用不会丢失)
  • 内存分配

    地址分类说明
    虚拟地址用户编程时将代码分成若干个段,每段代码的地址 = 段名称 + 段内相对地址
    逻辑地址虚拟地址中的段内相对地址
    物理地址实际物理内存中所看到的存储地址

内存管理

堆区的内存是由程序员申请使用,大多数现代编程语言为了提高编程效率,都实现了自动内存管理。而垃圾回收 GC(Garbage Collection)是自动内存管理中常使用的技术。

垃圾回收技术说明
标记清除 (Mark - Sweep)标记阶段遍历内存区域,标记对象的是否还存活;清除阶段,将已不在使用的内存区域回收清理。
引用计数 (Reference Counting)顾名思义,堆每块分配的内存区域对象设置一个计数器。每当引用一次该内存区域数据时,进行计数器 +1,不使用时进行 -1。当引用计数为 0 时表明该区域内存可以回收。引用计数会存在循环引用的问题。
分代回收 (Generational)将内存划分为若干代(新生代、老年代),经过多次垃圾回收仍然存活的对象会被移动到老年代区域。
编程语言内存管理
C++C++ 11 之后添加了智能指针,使用引用计数计数
Java分代垃圾回收技术
Python以引用计数为主,结合使用标记清除和分代回收技术
Swift自动引用计数 ARC(Automatic Reference Counting)
JavaScript标记-清除(Mark Sweep)技术

内存对齐

先说说数据的内存分布,基础的数据类型来说,Int 在 32 位机器上占用 4 字节,在 64 位机器上占用 8 个字节;Float 占用 4 个字节;Double 占用 8 字节;Bool 占用 1 字节;String 占用 16 字节。struct 结构体的内存占用与其包含的数据类型的占用有关系;class 是引用类型,所以内存占用就是指针内存占用,在 32位机器上占用 4 字节,64 位机器上占用 8 字节。

还需要了解一个概念就是内存与 CPU 的交互。我们常说的 32 位、64位指的就是计算 CPU 的最大计算能力,也就是 CPU 上的寄存器的宽度。而 CPU 与内存交互调度的过程中,数据总线调度的最大宽度就是寄存器的这个宽度。为了提高 CPU 读取内存效率,最好在一个时钟周期内,读取数据的宽度刚好为这个宽度为最优。

数据类型根据类型不同,占用的字节数可能存在不会刚好等于这个宽度。而“内存对齐”就是为了解决这个问题的。为了提高 CPU 读取内存效率,64 位机器来说,会将数据按照 8 字节的倍数进行对齐和排列,这样 CPU 在读取过程会,数据的起始地址都是 8 的倍数(例如 Float 类型数据只占用 4 个字节,后面会填充 4 个字节的空隙,后面再布局其它类型数据)。这样会照成一定的内存浪费,但是提高了 CPU 处理速度,是一种以空间换时间的优化方案。

内存泄漏

对于计算机来说内存是有限了,程序使用内存时,如果申请了内存而未释放,这就会引起内存泄漏。通俗一点理解,将内存比如成一堆砖头,如果使用内存的码农都只借砖头,而不将砖头换回来,最后就会造成计算内存不足。