深入理解JavaScript内存管理和GC算法
背景介绍
JavaScript是一门非常灵活多用途的语言,这得益于JavaScript内部的垃圾回收机制以及自动内存管理机制。不仅如此,了解这些机制将有助于我们编写出高效且易于维护的代码。
内存管理方法
JavaScript中,内存管理主要通过两种方法进行:栈(stack)和堆(heap)。
栈(Stack)
栈是一种有限制的数据结构,它存储了所有基本类型 (undefined,null,布尔类型,数字类型和字符串类型) 的变量,它们都是直接存在栈中的。当我们创建一个新的变量时,它就会被放到栈的顶部。当变量失去可访问性时,栈就会自动清除变量。
堆(Heap)
堆是开辟的一块内存区域,用于存储所有引用类型的变量。与栈不同的是,堆中的数据大小不固定,我们可以动态地创建对象。这意味着内存必须动态地分配,并在不再需要它们时释放。
垃圾回收机制
引用计数(reference counting)
垃圾回收的首要策略是引用计数(reference counting)。它是通过引用计数器实现的,每当有一个变量引用对象时,计数器就会加1,相反地,当对象不再被使用时,计数器就会减1。当计数器归零时,JavaScript会将对象从内存中移除。
但是,引用计数也存在一个大问题是循环引用。即使对象已经成为垃圾,仍有多个对象引用该变量,那么该对象就不能被回收。以下是一个例子:
let a = {}
let b = {}
a.foo = b
b.bar = a
以上代码中,a
和 b
都指向对象 {}
,并且互相引用,就算再没有变量引用他们,他们仍然不会被垃圾回收,因为他们的引用计数不为0,这就是引用计数算法的局限性。
标记-清除(mark and sweep)
另一种垃圾回收技术是标记-清除(mark and sweep)。这种技术适用于解决引用计数算法的问题,在JavaScript中,标记-清除(makr and sweep) 算法正是所采取的垃圾回收策略。
标记-清除的流程如下:
- 从根节点开始(全局对象
window
)遍历所有的对象,标记所有能够从根节点到达的对象(可达对象)。 - 遍历所有的对象,清楚所有未标记的对象(不可到达的对象)。
- 对于所有被标记的对象进行垃圾回收,释放被占用的内存。
示例1
function foo() {
let a = { 'name': 'John' }
let b = { 'name': 'Jane' }
a.friend = b
b.friend = a
}
foo();
对上文的代码进行一下解析:
函数内创建了两个对象a、b。然后在a上定义了一个friend属性,并将它赋值为b。同样,b也有一个friend属性,指向a。这是一个循环引用。函数执行完毕后,这两个对象将会成为不可达的(没有变量引用它们),并且标记被删除。
总结
JavaScript提供了垃圾回收机制来确保内存占用的有效管理。我们可以看到,垃圾回收机制并不完美。对于循环引用,标记-清除算法可以解决这个问题。因此,当我们使用引用类型(对象、函数、数组等)时,一定要注意内存泄漏问题,以保证页面可以良好地运行。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:深入理解JavaScript内存管理和GC算法 - Python技术站