垃圾回收的必要性?
在程序中,我们定义了许多变量,变量存储的在内存中,这时就需要一套机制来管理内存。在javascript中,并没有提供对应的api给开发者直接手动控制内存的申请和回收,因为javascript程序中的变量的内存空间的申请和释放都是javascript自动处理实现垃圾回收机制。
内存的垃圾回收机制是十分必要的。在程序中,字符串、数组和对象没有固定的大小,所以只有当他们大小定义好并已知时,才会动态的给他们分配内存存储空间,那么最终使用完之后都需要释放对应的内存,否则javascript持续分配却不释放内存的话,将会消耗完系统中所有可用的内存,造成系统崩溃。
垃圾回收机制
那么,javaScript垃圾回收的机制是怎样的呢?其实就是,找出不再使用的变量,然后释放其占用的内存,但是这个过程并不是实时的,因为实时轮询的开销会比较大,所以垃圾回收会按照固定的时间间隔周期性的执行。
那么垃圾回收机制怎么知道哪些内存不再需要呢?垃圾回收有两种方法:标记清除、引用计数。引用计数比较少用,在现代浏览器中不再使用,标记消除比较常用,现代浏览器使用的都是这种方式,本文主要讲解标记清除方式。
标记清除流程为:当变量进入执行环节时,就标记这个变量‘进入环境’,从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为当执行流进入响应的环境,就有可能会用到这些变量。当变量离开环境,则将其标记为‘离开环境’,然后在一定周期收,自动垃圾收集机制会把这部分内存回收释放。
比如:
在上述代码中,a、b、c变量在执行add函数的时候,均被标记为‘进入环境’,当add函数执行完之后,则a、b、c变量均被标记为‘离开环境’,在一定周期之后,自动垃圾收集机制会把这部分变量占用的内存回收释放。
内存泄漏
虽然javascript已经有了垃圾回收机制,但是如果我们编写代码不恰当,那么很容易让变量一直被标记为‘进入环境’,一直无法回收,造成内存泄漏与浪费。
以下几种情况是比较容易造成内存泄漏的:
意外的全局变量
在上述代码中,bar没声明就直接使用,在非严格模式下,会变成一个全局变量,一直占用内存不会被垃圾回收器释放。
还有另一种是函数中的this创建的:
foo 函数调用的时候,在非严格模式下,this 指向了全局对象(window),意外的创建了全局变量。
要避免上述两种情况,只需要在 JavaScript 文件头部加上 'use strict',启用严格模式解析 JavaScript ,避免意外的全局变量。
计时器的疏忽
上述代码运行完之后,定时器还是会存在。同时由于setInterval回调函数中对someResource的引用,使得someResource占用的内存也不会被释放。
闭包(如果还没学过闭包,可先忽略这种情况)
由于IE的js对象和DOM对象使用不同的垃圾收集方法,因此闭包在IE中会导致内存泄露问题。
代码片段做了一件事情:每次调用 replaceThing ,theThing 得到一个包含一个大数组和someMethod函数的新对象。同时,变量 unused 是一个引用 originalThing 的闭包(先前的 replaceThing 又调用了 theThing )。思绪混乱了吗?最重要的事情是,闭包的作用域一旦创建,它们有同样的父级作用域,作用域是共享的。someMethod 可以通过 theThing 使用,someMethod 与 unused 分享闭包作用域,尽管 unused 从未使用,它引用的 originalThing 迫使它保留在内存中(防止被回收)。当这段代码反复运行,就会看到内存占用不断上升,垃圾回收器(GC)并无法降低内存占用。本质上,闭包的链表已经创建,每一个闭包作用域携带一个指向大数组的间接的引用,造成严重的内存泄漏。
修复改例子闭包内存泄漏的方法很简单:
在 replaceThing 的最后添加 originalThing = null,标记originalThing,这样在其才会被垃圾回收器回收,释放占用的内存。
没有清理的DOM元素引用
在上述代码中,我们使用变量button获取一个dom节点之后,调用了web api去删除这个按钮。但是这个dom元素依然被button这个变量引用,所以对应的内存并没有被释放,换言之,DOM元素还在内存里面。此时只需要button =null即可释放内存。
如何识别内存泄漏
我们可以借助浏览器来识别内存泄露。
新版本的chrome在 performance 中查看:
步骤:
打开开发者工具 Performance
勾选 Screenshots 和 memory
左上角小圆点开始录制(record)
停止录制
在图中的红框,也就是HEAP部分,可以看到内存占用值在周期性的回落,也可以看到垃圾回收的周期。值的注意的是,如果垃圾回收之后最低值在不断上涨,那么肯定是有比较严重的内存泄露问题。
可通过垃圾回收优化代码的情景
数组优化
在编程中,有时候我们需要清空数组,大部分做法是arr =[],这种做法会创建一个新的空对象,会产生一小片内存垃圾。更加优化的做法是,直接将原来数组的长度设置为0即可,也就是arr.length = 0,这样既能清空数组,又能同时实现数组重用,减少内存垃圾的产生
尽量复用对象
在循环中,我们很容易创建新对象去记录某些值。优化的原则是,能复用的对象尽量复用,不用的对象,尽可能设置为null,使其可被垃圾回收。
通用的函数尽量放在循环外部
错误的示例
优化的示例