You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
在内存管理环境中,对象 A 如果有访问对象 B 的权限,叫做对象 A 引用对象 B。引用计数的策略是将“对象是否不再需要 ”简化成“ 对象有没有其他对象引用到它,如果没有对象引用这个对象,那么这个对象将会被回收。
letobj1={a: 1};// 一个对象(称之为 A)被创建,赋值给 obj1,A 的引用个数为 1 ,很显然引用次数不为0, 无法垃圾收集letobj2=obj1;// A 的引用个数变为 2obj1=0;// A 的引用个数变为 1obj2=0;// A 的引用个数变为 0,此时对象 A 就可以被垃圾回收了
前言
无论高级语言,还是低级语言。内存的管理都是:
释放内存处理方式,各种语言都有自己的垃圾回收(garbage collection, 简称GC)机制。做GC的第一步是判断堆中存的是数据还是指针,是指针的话,说明它被指向活跃的对象。有3种判断方法:
Conservative
:存储格式是地址,C/C++
有用到这种算法。Compiler hints
:对于静态语言,比如Java
,编译器是知道它是不是指针的,所以可以用这种。Tagged pointers
:JavaScript
用的是这种,在字末位进行标识,1为指针。JavaScript 内存问题
内存泄漏
什么情况下会内存泄漏
memory leak
?可以这么理解,就是有些代码本来应该要被回收的,但是没有被回收,所以一直占用着操作系统的内存,从而越积越多。一般的内存泄漏其实无关紧要,可怕的是内存泄漏引起的堆积,导致GC一直没办法使用所占用的内存给其他程序使用。内存溢出
内存溢出
out of memory
,是指程序在申请内存时,没有足够的内存空间供其使用,出现 out of memory;比如申请了一个 integer, 但给它存了 long 才能存下的数,那就是内存溢出。注意:
memory leak
会最终会导致out of memory
JavaScript 垃圾管理
JavaScript
有自动垃圾收集机制,找出不再继续使用的值,然后释放其占用的内存。垃圾收集器会每隔固定的时间段就执行一次释放操作。 在JavaScript
中,最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的,因此a = null
其实仅仅只是做了一个释放引用的操作,让a
原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。JAVASCRIPT
对象都是通过堆来进行内存分配的。当我们在代码中声明变量并赋值时,V8 引擎就会在堆内存中分配一部分给这个变量。如果已申请的内存不足以存储这个变量时,V8 引擎就会继续申请内存,直到堆的大小达到了 V8 引擎的内存上限为止(默认情况下,V8引擎的堆内存的大小上限在 64 位系统中为1464MB
,在32位系统中则为732MB
)。JAVASCRIPT
对象进行分代管理。新生代:新生代即存活周期较短的JAVASCRIPT
对象,如临时变量、字符串等; 老生代:老生代则为经过多次垃圾回收仍然存活,存活周期较长的对象,如主控制器、服务器对象等。javascript回收方法
V8 引擎中使用两种优化方法:
GC
;GC
的耗时。回收算法:
大部分垃圾回收语言用的算法称之为
Mark-and-sweep
:(1) 引用计数 (reference counting)
在内存管理环境中,对象 A 如果有访问对象 B 的权限,叫做对象 A 引用对象 B。引用计数的策略是将“对象是否不再需要 ”简化成“ 对象有没有其他对象引用到它,如果没有对象引用这个对象,那么这个对象将会被回收。
mozilla 文档 中很形象的一个例子:
当对象被引用次数为 0 时,就被回收。潜在的一个问题是:循环引用时,两个对象都至少被引用了一次,将不能自动被回收,导致内存泄露。
当函数 func 执行结束后,返回值为 undefined,所以整个函数以及内部的变量都应该被回收,但根据引用计数方法,obj1 和 obj2 的引用次数都不为 0,所以他们不会被回收。
要解决循环引用的问题,最好是在不使用它们的时候手工将它们设为空。上面的例子可以这么做:
(2)标记清除 (mark and sweep)
这是当前主流的
GC
算法,从2012年起,所有现代浏览器都使用了** 标记-清除(Mark-Sweep)**垃圾回收算法。所有对 JavaScript 垃圾回收算法的改进都是基于 标记-清除算法 的改进,并没有改进 标记-清除算法 本身和它对“对象是否不再需要”的简化定义。Mark-Sweep(标记清除)
分为标记
和清除
两个阶段,在标记阶段会遍历堆中的所有对象,然后标记活着的对象,在清除阶段中,会将死亡的对象进行清除。当对象,无法从根对象沿着引用遍历到,即不可达(unreachable),进行清除。
JAVASCRIPT
中有个全局对象,浏览器中是 window。定期的,垃圾回收期将从这个全局对象开始,找所有从这个全局对象开始引用的对象,再找这些对象引用的对象...对这些活着的对象进行标记,这是标记阶段。清除阶段就是清除那些没有被标记的对象。常见 JavaScript 内存泄漏
1.意外的全局变量(全局变量不会被标记清除法清除)
JavaScript 处理未定义变量的方式比较宽松:未定义的变量会在全局对象创建一个新变量。在浏览器中,全局对象是
window
。解决方法:
2.被遗忘的计时器或回调函数
3.闭包递归
闭包的特性:1.函数嵌套函数,2.函数内部可以引用外部的参数和变量,3.参数和变量不会被垃圾回收机制回收
闭包由于存在变量引用其返回的匿名函数,导致作用域无法得到释放。 最新版本的浏览器中,可以通过标记清除的方式处理掉闭包的作用域导致的内存泄漏,但闭包的变量(匿名函数、参数变量)会常驻内存,会造成一定的性能问题
内存剖析工具方法
Chrome浏览器方法
Chrome 提供了一套很棒的检测 JavaScript 内存占用的工具
Memory
, 提供Heap snapshot
(堆内存截图)、allocation instrumentation on timeline
( 内存timeline上的分配检测)、allocation sampling
(内存分配抽样)Node 命令行查看内存状态方法
process.memoryUsage
返回一个对象,包含了Node
进程的内存占用信息。该对象包含四个字段,单位是字节,含义如下。heapTotal
:"堆"占用的内存,包括用到的和没用到的。heapUsed
:用到的堆的部分。external
: V8 引擎内部的 C++ 对象占用的内存。判断内存泄漏,以
heapUsed
字段为准。WeakSet 和 WeakMap
通过前几个内存泄漏示例我们会发现如果我们一旦疏忽,就会容易地引发内存泄漏的问题。及时清除引用,回收内存非常重要。但是实际生产过程中,有可能不清楚上下文,导致内存泄漏。最好能有一种方法,在新建引用的时候就声明,哪些引用必须手动清除,哪些引用可以忽略不计,当其他引用消失以后,垃圾回收机制就可以释放内存。这样就能大大减轻程序员的负担,只要清除主要引用就可以了。
ES6 考虑到了这一点,推出了两种新的数据结构:
WeakSet
和WeakMap
。它们对于值的引用都是不计入垃圾回收机制的,弱引用。上面代码中,先新建一个
Weakmap
实例。然后,将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在WeakMap
里面。这时,WeakMap
里面对element的引用就是弱引用,不会被计入垃圾回收机制。也就是说,DOM 节点对象的引用计数是1,而不是2。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。
Weakmap
保存的这个键值对,也会自动消失。基本上,如果要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用
WeakMap
。参考阅读:
JavaScript深入理解之垃圾收集
阮一峰-JavaScript 内存泄漏教程
阮一峰-JavaScript 运行机制详解:再谈Event Loop
4类 JavaScript 内存泄漏及如何避免
10分钟了解JS堆、栈以及事件循环的概念
(js队列,堆栈) (FIFO,LIFO)
从Promise到Event Loop
Node.js 内存管理和 V8 垃圾回收机制
The text was updated successfully, but these errors were encountered: