一、内存管理的介绍
1.1 内存管理是什么
当一个软件运行在一个目标操作系统时,他需要访问计算机的RAM(Random-access memory)来:
- 加载需要执行的字节代码
- 存储执行程序需要的数据值和数据结构
- 加载运行程序所需要的其他运行时系统
当一个软件项目使用内存时,有两块区域的内存,除了加载字节代码所需的空间,还有栈内存和堆内存。
1.2 栈
栈是用于静态内存分配,栈是后进先出。
- 从栈中存储和检索数据是非常快的,因为不需要检查,你只需要从最顶层存储和检索数据即可。
- 这也意味着存储在栈上的数据必须是有限的和静态的(在编译期间数据的大小是已知的)。
- 函数的执行数据是存储在栈帧(这是真正的执行栈)上。每一帧都有一块空间用来存储执行函数所需的数据。例如,每次函数中声明一个新的变量,他被‘push’ 进了栈的最顶层块区。当每一次函数退出的时候,最顶层的区块也被清除,所有被函数push进栈里的变量也被清理。这些可以在编译时确定,因为这里存储的数据是静态的。
- 多线程应用会在每一个线程都有一个栈。
- 栈的内存管理是比较简单和直接的,有操作系统直接控制。
- 一些存储在栈中的典型数据比如 本地变量(值类型,原始类型,基础常量),指针和函数帧。
- 有时会遇到栈溢出的问题,是因为与堆相比栈是有大小限制的。
- 对于大多数的编程语言,存储在栈上的值都有大小的限制。
1.3 堆
堆是用于动态内存分配,与栈不同的是,堆需要使用指针检索数据。
- 堆比栈要更慢,因为堆有检索数据的过程,但是堆会存储比栈更多的数据。
- 这样意味着存储的数据大小是动态的。
- 堆在应用的多个线程是共享的。
- 由于堆是动态的特性,所以对堆的管理是讲究技巧的,这也是一些内存管理问题的来源和一些自动内存管理解决方案的由来。
- 一些存储在堆中的典型数据例如全局变量,引用类型例如对象、字符串、maps和其他复杂数据结构。
- 这也是为什么你会遇到 内存不足的问题,当你的应用试图使用比已分配的堆更多的内存的时候(尽管目前有很多的解决方案 比如垃圾回收)
通常情况下,存储在堆中的数据是没有大小限制的。当然,最终是要取决于你的应用程序分配了多大的内存。
1.4 为什么内存管理这麽重要
和硬件驱动不一样,RAM不是无限的。如果一段程序一直消费内存而不释放它,最终会耗尽内存,而导致崩溃,更严重的是会导致操作系统的崩溃。所以软件程序不能一直占据着RAM,这会导致其他的项目和运行的过程耗尽内存空间。很多的编程语言都提供了自动的内存管理方案 ,来替代开发者手动管理。当我们谈论内存管理时,大多数情况下讲的堆内存的管理。
1.5 不同的方案
由于现代编程语言不想加重终端开发者管理应用程序的负担,所以设计了自动内存管理的方案。很多更老的编程语言仍然需要手动内存管理,但是他们提供了简单的方式。一些语言会采取多种内存管理的方式,有的甚至会让开发者自己选择那种对他们是最好的。方案如下:
1.6 手动内存管理
这些语言并不会默认为你管理内存,这有你来决定,是分配还是释放你所创建的对象。例如,
C和C++,他们提供了malloc,realloc,calloc, 和free的方法来进行内存管理,这有开发者来决定在项目中是分配还是释放堆内存,并且有效的利用指针来管理内存。1.7 垃圾回收GC
自动的堆内存管理是释放不再使用的内存分配。GC是现代编程语言中最常见的内存管理,这个过程通常运行在特定的间隔,并且可能引起小的过载叫做暂停时间。JVM(Java/Scala/Groovy/Kotlin), JavaScript, C#, Golang, OCaml, and Ruby 这些语言是把GC作为默认内存管理。

标记清除GC。也叫做跟踪GC。通常情况下,有两部分的算法组成,首先把仍然被引用的对象标记为 ‘活动的’,下一个阶段会释放不活动的对象的内存空间。JVM, C#, Ruby, JavaScript, 和 Golang应用这种方式。在JVM中有多中不通的GC算法可以选择,而JS引擎例如V8是标记清除GC和引用计数GC共同实现的。这种方式也应用在C和C++中作为外部库。
- 引用计数GC。在这种方式下,每一对象都有一个引用计数,引用计数根据引用的变化增长或者减少,当引用计数变成0,垃圾回收就完成了。有一点不推荐就是他不擅长处理循环依赖。PHP, Perl, 和 Python,这些语言会使用这类型的GC和一些变通的方法来克服循环依赖。这类GC也被应用在C++中。
Resource Acquisition is Initialization (RAII)
资源获取即初始化
这类型的内存管理,一个对象的内存分配是和他的生命周期绑定的,从开始创建到最终销毁。是有C++引入的,也用于Ada和Rust等语言中。Automatic Reference Counting(ARC)
自动引用计数
这和引用计数类似,但不是通过运行一个运行时步骤在一个特定的时间间隔,在编译期间retain和release指令会被插入到编译代码中,当一个对象的引用计数变为0会被自动清理这作为执行的一部分而没有任何的程序暂停。他仍然不能处理循环依赖的问题,而是依赖开发者处理,并使用特定的关键字。这是Clang编译器的特色,并给Object C 和Swift 提供了ARC。Ownership
通过整合RAII并有一个ownership模型,任何的值都必须有一个变量作为他的owner(而且一次只有一个owner),当这个owner超出范围,值会被立刻删除并释放内存,而不必考虑是堆内存还是栈内存。这有一点像编译器的引用计数。Rust是应用这种方式的典型。

前置:
