内存管理问题
内存管理是编程过程中的一个经典问题,早期在 C 语言时代,几乎都靠 malloc/free 手动管理内存。随着各个平台的发展,到现在被广泛采用的主要有两个方法:
- 引用计数 (ARC,Automatic Reference Counting)
- GC (Garbage Collection)
管理方法 ARC/GC
因为 Java 的流行,GC 被广泛的认知。GC 简单的说是定期查找不再使用的对象,释放对象占用的内存。
基于 GC,申请的对象不需要手动释放,只需要确认对象在不再需要时,不再被其他对象引用。
引用计数早期主要用于底层系统,比如文件系统的 inode 管理,后来 C++ 的 boost 库实现了一套完整的 ARC,目前流行的系统还有 Objective C 也是采用的 ARC。
ARC 的特点是,一个对象被引用时,引用计数增加 1,引用对象释放时,引用计数减少 1,如果引用计数为 0,释放对象。
比较
因为 ARC 和 GC 的不同策略,对编程几个方面的影响如下。
性能
GC 需要一套额外的系统跟踪分配的内存,分析哪些内存需要释放,相对来说就需要更多的计算。这也是为什么对性能敏感的场景不采用 GC 的原因,比如,高性能的服务端程序,资源有限的嵌入式设备(iOS 就没有采用 GC)。
ARC 由开发者自己来管理资源在什么时候释放,不需要额外的资源,所以性能没有损失。
延迟
GC 回收内存时,需要完全暂停当前程序,这会给程序带来难以预测的一个延迟期。如果需要回收的资源很多,这个延迟可能会非常大。
ARC 在资源引用为 0 时立即释放,没有不可预测的延迟。
编程难度
不难看出,GC 在性能、延迟等方面有明显的缺点,为什么 GC 还会被广泛采用呢?
GC 带来的最大好处是不需要开发者手动管理内存分配,这大大降低了编程难度,同时可以大幅减少跟内存管理相关的 Bug:
- 悬空指针。指针指向的内存被其他代码释放
- 重复释放内存
- 内存泄漏。申请的内存没释放
不过使用 GC 并不代表可以完全不用理解内存管理,如果对象的引用关系跟想象的不一致,GC 也会有内存泄漏的问题
。
我们之前理解的内存泄漏
是指一个分配的内存没有被释放造成的。而 GC 平台下的内存泄漏是指对象有引用而开发者不知道
,比如:
ObjectA -> ObjectB
ObjectB 使用完后,我们没有及时把 ObjectA 引用 ObjectB 的指针设置为 NULL,这时, ObjectB 不会被 GC 回收。
时机
性能
延迟
编程难度
ARC |
引用计数为 0 马上回收 |
快 |
小 |
较大 |
GC |
定时扫描清理 |
慢 |
大 |
较小 |
怎么选择 ARC or GC
开发一个项目时,采用什么样的平台,跟实际面对的场景有很大关系,没有一个技术是用来解决所有问题的。
一般来说,对延迟和性能不敏感的系统,可以考虑带 GC 的平台,比如 Java、Go 等来开发,通常可以提高开发效率。
如果需要对系统的性能有良好的控制,或者平台的资源有限,ARC 是更好的选择。比如操作系统、数据库等选择 C 或者 C++。比如 iOS 的 Object C 就是采用 ARC,实际来看比使用 Java (GC) 的 Android 平台的表现要好太多。
但是 ARC 平台一般对开发者要求要更高。
最近出现的新语言Rust
采用的是 ARC,但是 Rust 会在代码编译阶段对内存、指针的使用做严格的分析和检查,确保程序没有内存管理问题。相当于把 GC 的一部分工作移到编译阶段,这样程序的运行性能几乎没有损失,同时又大大减少内存管理相关的 Bug。
我的观察从C++11
正式吸纳boost
的 smart pointer
后,C++ 在内存管理方面比之前有极大的提升,如果严格的按照 smart pointer 的规范,同样可以减少内存管理的风险。Rust 就有点像一个严格的 C++11 编译系统。
支持 GC 的平台里面有一个特殊的,就是 Erlang
。Erlang 的 GC 是进程级别的(Erlang 的轻量级进程),意味着 GC 发生时,只暂停当前进程,其他进程不受影响。另外,Erlang 程序往往会运行海量的进程,相当于把 GC 分散开了,所以 Erlang 的 GC 一般不会产生明显的延迟。
了解这些细节,在面对具体问题时,能帮你做出正确的选择。
欢迎来微博留下您的意见:http://weibo.com/2255454164/E48lBE9pU
作者:张虎
weibo: @Tiger_张虎, 云巴 (yunba.io) 创始人,yunba.io 云端实时消息服务。 JPush 创始人,原CTO。 Oracle VM 创始团队成员。
<script type="text/javascript">
$(function () {
$('pre.prettyprint code').each(function () {
var lines = $(this).text().split('\n').length;
var $numbering = $('<ul/>').addClass('pre-numbering').hide();
$(this).addClass('has-numbering').parent().append($numbering);
for (i = 1; i <= lines; i++) {
$numbering.append($('<li/>').text(i));
};
$numbering.fadeIn(1700);
});
});
</script>
分享到:
相关推荐
引用计数是一种常见的垃圾回收策略,其基本思想是为每个对象维护一个引用计数,记录该对象被多少个其他对象所引用。当一个对象的引用计数变为0时,表明没有其他对象指向它,可以推断此对象不再被使用,垃圾收集器就...
**引用计数法**是一种简单的垃圾回收策略,每个对象有一个引用计数,当计数为0时,表示对象不再被使用,可以被回收。然而,这种方法不能处理循环引用问题,因此现代JVM大多使用其他垃圾回收策略,如标记-清除、复制...
当对象的引用计数下降到0,除了通过接口引用的对象外,GC会判断这个对象是否还有其他引用。如果没有,GC会释放该对象占用的内存。这与C#或Java中的引用计数不同,它们的垃圾回收机制不依赖于接口计数。 4. 多线程...
GC主要基于两种概念:引用计数和可达性分析。 1. 引用计数:为每个对象维护一个引用计数,当引用数为0时,对象被视为垃圾。但这种方法无法处理循环引用的问题。 2. 可达性分析:从一组称为“根”(如栈上的本地...
2.1. 线柱2.2. JVM内存区域2.2.1. 程序计数器(线程私有).2.2.2. 虚拟机栈(线程私有)..... 引用计数法.2.4.1.2. 可达性分析.2.4.2. 标记清除算法(Mark-Sweep)2.4.3. 复制算法(copying)2.4.4. 标记整理算法(Mark-Comp
1. 引用计数法:跟踪对象被引用的次数,当引用计数为0时,认为对象可回收。但由于其无法处理循环引用,Java并未采用。 2. 可达性分析:通过一系列称为“根”(如:本地方法栈中的JNI引用,运行时常量池的静态引用,...
#Luarc-5.1 在 Lua 5.1.4 中实现的引用计数内存管理和 Mark/Sweep GC 的实现。 #执行使用纯引用计数实现,即一旦引用计数变为 0,堆对象将被回收,不使用延迟释放。 是的,由于引用计数更新,更多的cpu周期用于参数...
然而,引用计数法存在无法解决对象之间循环引用的问题,即两个对象相互引用,导致它们的引用计数器都大于0,即使这样它们也无法被任何外部对象引用到。 2. 可达性分析法 可达性分析法通过一种称为“GC Roots”的根...
引用计数法实现简单,但处理循环引用时效率较低,且无法处理跨进程的引用。 d. 分代式垃圾回收法(Generational GC) 根据对象生命周期的不同,将内存划分为新生代、老年代等区域。新生代的对象存活时间短,GC...
常见的算法有引用计数法和可达性分析法。引用计数法简单直接,但难以处理循环引用的情况。可达性分析法则能更准确地判断对象是否存活,是现代JVM普遍采用的方法。 2. **确定何时执行GC**:JVM会在某些特定条件触发...
Java的垃圾收集机制是基于引用计数和可达性分析算法的,其目标是回收那些不再被程序中任何“活动部分”引用的对象所占用的内存,以优化内存使用并防止程序因内存耗尽而崩溃。 1. **工作原理**: - **引用计数**:...
1. **引用计数(Reference Counting)**:这是一种较早的回收算法,通过增加或减少对象的引用计数来决定是否回收该对象。然而,这种方法无法解决循环引用的问题。 2. **标记-清除(Mark-Sweep)**:此算法分为两个...
- 引用计数算法:此算法简单直观,但无法处理循环引用问题,例如对象A引用对象B,B引用A,两者引用计数都不会为0,导致无法正确回收。 - 可达性分析:Java采用这种方式,从一组称为GC Roots的对象出发,遍历其引用...
1. 引用计数:每个对象有一个引用计数,当计数为0时对象被视为垃圾。但这种方法无法处理循环引用的问题。 2. 跟踪收集(标记-清除):从根节点开始遍历所有引用,标记所有可达对象,未被标记的对象则被视为垃圾。...
引用计数法............................................................................................................................................... 26 2.4.1.2. 可达性分析..........................
`gc`模块提供了手动触发垃圾回收的方法,例如`gc.collect()`,但通常不推荐频繁使用,因为Python的垃圾回收机制通常是自动的。 弱引用在处理大型数据结构、缓存管理和复杂对象网络时尤其有用,因为它可以帮助防止...
1. **引用计数算法**:这是一种简单的垃圾收集算法,为每个对象维护一个引用计数。当对象被引用时计数加1,引用失效时减1。当计数为0时,对象可被回收。但由于循环引用的问题,即两个对象相互引用但无其他引用,引用...
1. **对象的可达性分析**:垃圾收集器通过一系列的可达性分析算法,如引用计数法或根可达算法,来判断哪些对象是“活动的”(即仍然被程序中的其他对象引用),哪些是“无用的”(没有被任何活动部分引用)。...