老是听到有人说内存泄漏,但是我做开发这么多年,除非我故意去测试内存是怎么泄漏的,这个时候才会内存泄漏,其他情况下几乎没有遇到过内存泄漏的问题。然后就一直思考我没遇到内存泄漏的问题是不是跟我做的业务体量太小有关系?带着问题去找答案,今天就来聊聊内存泄漏。
// 堆代码 duidaima.com // 顶部定义变量 var a = 1; // 顶部定义函数 function fn() { // 不适用 var 定义变量 b = 2; } // dom 引用,在顶部定义,并且没有使用 var 定义 dom = document.getElementById('dom'); // 全局变量 var transmission = { a: 1, b: 2 }
上面这些情况大家应该都很常见,这些都是可能会造成内存泄漏的原因之一,但是用了这么多年也没见过内存泄漏的问题,这是为什么呢?
上面说了内存泄漏的原因,但是要搞明白为什么会内存泄漏,就得先了解一下什么是内存。内存就是我们在定义变量的时候,给变量分配的空间,这个空间是有限的,当我们定义的变量超过这个空间的时候,就会造成内存溢出。内存泄漏就是我们定义的变量没有被释放,导致内存空间被占用,当我们定义的变量超过这个空间的时候,就会造成内存溢出。所以内存泄漏的本质指的是内存溢出,只不过内存溢出也有很多原因,内存泄漏只是其中的一种。
// 堆代码 duidaima.com function cache() { var cache = {}; return function(key, value) { if (cache[key] == null) { cache[key] = value; } return cache[key]; } } var _cache = cache(); setInterval(function() { _cache(Math.random().toString(36).substr(2), 0); }, 100);上面这个例子是一个简单的缓存函数,我们可以通过这个函数来缓存一些数据,但是这个函数有一个问题,就是缓存的数据一直不会被释放,导致内存泄漏。我们一秒钟就会缓存一个数据,这样就会导致内存一直增加,我们可以通过Performance来查看内存的使用情况,如下图:
可以看到内存曲线是一只在增加,没用下降的趋势,这种情况一般都是内存泄漏。
上面我们已经知道了什么是内存泄漏,但是我的标题说内存泄漏并不是我们需要关心的,这里就要说到V8的垃圾回收机制了,在网上查资料大多数人都能查到浏览器的垃圾回收机制分为两种,一种是标记清除,一种是引用计数。标记清楚有bug,所以现在大多数都是引用计数,但是这两种都是浏览器的垃圾回收机制,V8的垃圾回收机制是分代回收。
分代回收指的是将内存分为新生代和老生代,新生代的内存空间比较小,老生代的内存空间比较大,新生代的内存空间会比较频繁的进行垃圾回收,当新生代的内存多次没有被回收的时候,就会被移动到老生代,老生代的内存空间比较大,所以垃圾回收的频率比较低,但是垃圾回收的时候会暂停js的执行,所以垃圾回收的频率不能太高,否则会影响js的执行。
其实垃圾回收和内存泄漏的关系并不大,它关心的是有没有不需要用的内存需要回收,但是上面说到了垃圾回收的时候会暂停js的执行,所以存在内存泄漏的时候,垃圾回收的执行时间会变长,这样就会影响js的执行,我们的页面就开始卡顿了。
我们不需要关心的内存泄漏是因为浏览器运行在客户端,上面说到了在内存泄漏的时候,垃圾回收的执行时间会变长,这样就会影响js的执行,我们的页面就开始卡顿了。作为一个普通用户,我发现页面突然变卡了,我这个时候直接刷新页面不就好了,这个时候内存会被重置,内存泄漏的问题就解决了。
所以老是纠结内存泄漏的问题,其实是不需要的,除了我上面说到的普通用户遇到卡顿的情况会直接刷新页面以外,浏览器在这一块也做了很多优化。例如Chrome浏览器,当tab页没有被激活的时候,页面将会被冻结,这个时候页面的js执行就会被暂停,等重新激活的时候,页面的js执行就会继续,这样就可以延长页面使用的时间,也就是延长了内存泄漏的时间,这样就可以避免内存泄漏的问题。
除了这个点以外,还有其他的优化,在通常情况除非你故意去制造内存泄漏,否则我们不需要关心内存泄漏的问题。
上面说到了不需要关心的内存泄漏,那么我们需要关心的内存泄漏是什么呢?我们普通的业务仔几乎不需要关心,但是有些特殊的业务仔就需要关心了,例如页游、直播、canvas、webgl等等。但是上面这些一样通过刷新页面就可以解决,需要关心,但是也可以接受,但是有一种情况就不一样了,那就是node。
node运行在服务端,内存是非常宝贵的,如果内存泄漏,那么就会导致内存越来越少,最后就会导致node服务挂掉,这个可是大事,node的内存泄漏和上面提到的内存泄漏是一样的,但是前端业务仔你需要写node的代码吗?所以前端业务仔不需要关心内存泄漏。
function cache() { var cache = {}; return function(key, value) { // 超过阈值,清除最早的缓存 const keys = Object.keys(cache); if (keys.length > 10) { delete cache[keys[0]]; } if (cache[key] == null) { cache[key] = value; } return cache[key]; } } var _cache = cache(); setInterval(function() { _cache(Math.random().toString(36).substr(2), 0); }, 100);上面清除最早的值是错误的写法,因为使用对象作为缓存,对象的键值顺序不是固定的,只是为了演示才这样写。上面设置阈值之后,内存不会一直增加了,最后会恒定在一个值,如下:
可以看到内存不会一直增加了,最后趋近于一个值,这样就避免了内存泄漏的问题。