内存管理
“内存”顾名思义就是我们平常所说的电脑内存条。专业术语:随机存取存储器 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 处理速度,是一种以空间换时间的优化方案。
内存泄漏
对于计算机来说内存是有限了,程序使用内存时,如果申请了内存而未释放,这就会引起内存泄漏。通俗一点理解,将内存比如成一堆砖头,如果使用内存的码农都只借砖头,而不将砖头换回来,最后就会造成计算内存不足。