javascript 内存泄漏

提纲

什么是内存泄漏

简介

1图,放一个CPU,内存,硬盘的图,一二级缓存就不讲了,不将计算机原理
CPU工作的时候:
  1、需要从存储器里取数据出来。
  2、进行运算,要不停地用存储器读写。
  3、计算出结果再返回到存储器里。
举例子形容关系,CPU一次能运算100条数据,硬盘有10万条数据,我总共要运算1000条数据大概10次,那么从硬盘取100条数据要1分钟,运算只要1ms,完了再写回去,又要1分钟,完了再取其他的数据,又得1分钟,这样来回10次需要就20分钟过去了,而如果是有内存的话,内存取一次1000条要1分钟,内存和硬盘的交互是1s一次,那么10次的数据都在这里,我2分钟就可以搞定所有了。

程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。
那么什么是内存呢?内存就是处于外存和CPU之间的桥梁,用于存储CPU的运算数据,这样内存就可以保持记忆功能,你写的所有代码,都是需要在内存上跑的,虚拟内存是从外存上分配的,很慢
内存的频率越高代表内存运算更快,同一块内存我跑的更快哟,这就是为什么DDR5比DDR3快的原因

2,内存分配溢出图
举例:在手机上,比如好医生app,系统初始的时候可能只会给你分配100m的内存,可以从android studio 上看到,这个时候你点击了某个图片列表页,这个时候内存就会暴涨,一旦接近临界值,程序就会去找操作系统说,我内存不够了,再给我点,系统就会又给你分配一段,完了你返回首页了,但是因为你的代码写的有问题,暴露各种全局对象啊,各种监听啊,一进一出多次,但是系统给每个app分配的内存是有上限的,直到内存不够分,泄漏导致的内存溢出。然后crash掉。我们早前在rn的scrollview上,就是因为这里,会挂掉。
out of memory
内存溢出是指程序在申请内存时,没有足够的内存空间供其使用,就会出现内存溢出。
memory leak
内存泄漏指的是你申请了一块内存,在使用后无法释放已申请的内存空间,比如程序会认为你可能会用到这个变量,就一直给你留着不释放,一次内存泄漏可以被忽略,但是内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

那么既然内存我可以申请,就可以被系统回收,在C语言中,需要程序员手动malloc去申请内存,然后free掉它,这写起来很麻烦,所以其他大多数语言都提供了自动回收的机制,那么既然自动回收了,就很容易出现各种问题。

内存泄漏的后果

1:安卓手机内存管理不好,导致只要不重启,时间越长,可用内存越少,即使杀程序。
2:导致内存溢出,如果手机内存被挤占的有限,那么手机会变卡,严重的自己crash掉,如果是pc端,浏览器的内存泄漏导致的溢出会让浏览器出现假死状态,只能通过强制关闭解决,如果是在webview上,比如我开始的时候写过一个代码在ios微信浏览器上调用swiper 的3d变换导致微信直接闪退。- -
3:这都是客户端的,客户端大多数情况下不会停留时间过长,所以除非是超级非常规操作,很少会出大问题,但是,跑在服务端的程序,通常都是一直跑几天甚至是几个月的,如果这个里面有代码不好的话,那么就会导致服务器宕机,必须重启。

引发内存泄漏的方式

1.意外的全局变量

JavaScript 对未声明变量的处理方式:在全局对象上创建该变量的引用(即全局对象上的属性,不是变量,因为它能通过delete删除)。如果在浏览器中,全局对象就是window对象。
如果未声明的变量缓存大量的数据,会导致这些数据只有在窗口关闭或重新刷新页面时才能被释放。这样会造成意外的内存泄漏。

为什么会对未声明的变量处理方式是挂window下呢?
“当引擎执行LHS查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎,前提是程序运行在非“严格模式”下”

摘录来自: Kyle Simpson、赵望野、梁杰. “你不知道的JavaScript(上卷)。” iBooks.
function foo(arg) {
  bar = 'this is hidden global variable';
}

等同于:

function foo(arg) {
  window.bar = 'this is hidden global variable';
}

另外,通过this创建意外的全局变量:

function foo() {
  this.variable = 'this is hidden global variable';
}
// 当在全局作用域中调用foo函数,此时this指向的是全局对象(window),而不是'undefined'
foo();
解决方案

正常的定义全局变量没有问题,但是这种是属于意外的泄漏,所以可以使用严格模式处理

2.console.log

传递给console.log的对象是不能被垃圾回收 ♻️,因为在代码运行之后需要在开发工具能查看对象信息。所以最好不要在生产环境中console.log任何对象。
追踪线上问题,console绝非是个好的方式。因为发生问题一般在用户哪里,你没办法看用户的日志。

function aaa() {
    this.name = (Array(100000)).join('*');
    console.log(this);
}
document.getElementsByClassName('console-obj')[0].addEventListener('click',function () {
      var oo = new aaa();
});
解决方案

可以删除自己的console.log,但是显然,在开发环境下,我就是想看我的console.log,所以可以判断下当前的环境是不是env,如果是product环境下的话,直接

window.console.log = function(){}

这样的手法不仅可以屏蔽console.log,还能防止别人在我们的页面下console.log调试
---保护本页面安全知识点

3.闭包(closures)

由于闭包的特性,通过闭包而能被访问到的变量,显然不会被内存回收♻️,否则就不会有闭包的说法了

    function foo() {
      var str = Array(10000).join('#');
      var msg = "test message";
      function unused() {
        var message = 'it is only a test message';
        str = 'unused: ' + str;
      }
      function getData() {
          return msg;
      }
      return getData;
    }
    var bar;
    document.getElementsByClassName('closure-obj')[0].addEventListener('click',function () {
        bar = foo();
    });
    // var list = [];
    // document.getElementsByClassName('closure-obj')[0].addEventListener('click',function () {
    //     list.push(foo());
    // });
  • 演示内存performance情况
  • 演示memory 情况
  • 断点演示闭包scope

闭包造成的内存泄漏占用会比其他的要多。
原因是在相同作用域内创建的多个内部函数对象是共享同一个变量对象(variable object)。如果创建的内部函数没有被其他对象引用,不管内部函数是否引用外部函数的变量和函数,在外部函数执行完,对应变量对象便会被销毁。反之,如果内部函数中存在有对外部函数变量或函数的访问(可以不是被引用的内部函数),并且存在某个或多个内部函数被其他对象引用,那么就会形成闭包,外部函数的变量对象就会存在于闭包函数的作用域链中。这样确保了闭包函数有权访问外部函数的所有变量和函数。了解了问题产生的原因,便可以对症下药了
---VO/AO知识点,call stack知识点

解决方案

不暴露到全局变量上,这样就不会有问题,暴露到全局变量上就手动置为null,垃圾回收器下次回来会带走你。

4.dom泄漏

在 JavaScript 中,DOM 操作是非常耗时的。因为 JavaScript/ECMAScript 引擎独立于渲染引擎,而 DOM 是位于渲染引擎,相互访问需要消耗一定的资源。如 Chrome 浏览器中 DOM 位于 WebCore,而 JavaScript/ECMAScript 位于 V8 中。假如将 JavaScript/ECMAScript、DOM 分别想象成两座孤岛,两岛之间通过一座收费桥连接,过桥需要交纳一定“过桥费”。JavaScript/ECMAScript 每次访问 DOM 时,都需要交纳“过桥费”。因此访问 DOM 次数越多,费用越高,页面性能就会受到很大影响。

为了减少 DOM 访问次数,一般情况下,当需要多次访问同一个 DOM 方法或属性时,会将 DOM 引用缓存到一个局部变量中。
但如果在执行某些删除、更新操作后,可能会忘记释放掉代码中对应的 DOM 引用,这样会造成 DOM 内存泄露。
---回流重绘知识点

    
  
// 因为要多次用到pre.wrapper、div.container、input.remove、input.add节点,将其缓存到本地变量中, var wrapper = document.querySelector('.wrapper'); var container = document.querySelector('.container'); var removeBtn = document.querySelector('.remove'); var addBtn = document.querySelector('.add'); var counter = 0; var once = true; // 方法 var hide = function(target){ target.style.display = 'none'; } var show = function(target){ target.style.display = 'inline-block'; } // 回调函数 var removeCallback = function(){ removeBtn.removeEventListener('click',removeCallback,false); addBtn.removeEventListener('click',addCallback,false); hide(addBtn); hide(removeBtn); container.removeChild(wrapper); wrapper = null; } var addCallback = function(){ let p = document.createElement('li'); p.appendChild(document.createTextNode("+ ++counter + ':a new line text\n")); wrapper.appendChild(p); // 显示删除操作按钮 if(once){ show(removeBtn); once = false; } } // 绑定事件 removeBtn.addEventListener('click',false); addBtn.addEventListener('click',false);

演示代码

    var refA = document.getElementById('refA');
    var refB = document.getElementById('refB');
    document.body.removeChild(refA);
// #refA不能GC回收,因为存在变量refA对它的引用。将其对#refA引用释放,但还是无法回收#refA。
refA = null;

// 还存在变量refB对#refA的间接引用(refB引用了#refB,而#refB属于#refA)。将变量refB对#refB的引用释放,#refA就可以被GC回收。
refB = null;</code></pre>

编程之家

5.计时器/监听器

var counter = 0;
    var clock = {
      start: function () {
        // setInterval(this.step,1000);
        if(!this.timer){
          this.timer = setInterval(this.step,1000);
        }
      },step: function () {
        var date = new Date();
        var h = date.getHours();
        var m = date.getMinutes();
        var s = date.getSeconds();
        console.log('step running');
      }
    }
    // function goo(){
    //     // clock = null;
    //     clearInterval(clock.timer);
    //     console.log('click stop');
    // }
    document.querySelector('.start').addEventListener('click',function () {
      clock.start();
      // document.querySelector('.stop').addEventListener('click',);
    });
    document.querySelector('.stop').addEventListener('click',function () {
      // clock = null;
      clearInterval(clock.timer);
    });

监听器没有及时回收或者是匿名回收导致的。
---bind,call,apply的区别

如何使用chrome performance

  1. 开启【Performance】项的记录
  2. 执行一次 CG,创建基准参考线
  3. 连续单击【click】按钮三次,新建三个 Leaker 对象
  4. 执行一次 CG
  5. 停止记录

以上就是我们使用的时候的步骤
那么对这个performances 里的各项是如何理解的呢
先问下回流和重绘,什么是回流,什么是重绘,以及为什么回流一定会导致重绘,但是重绘不会导致回流?

给一webkit下的浏览器渲染图,解释下怎么看

打开拼团列表页,直接跑一个performances

  • FPS 每秒的帧数,绿色条约稿,表示FPS值越高,通常上面附带红色块的帧表示该帧时间过长,可能需要优化。
  • CPU CPU资源,面积图表示不同事件对CPU资源的消耗。
  • NET 这个项和以前的不一样,查询相关资料也没有找到到底显示的是什么,所以只能通过下面的具体来看,HTML文件是蓝色条,脚本文件是黄色条,样式文件是紫色条,媒体文件是绿色条,其他的是灰色条,网络请求部分更详细的信息建议查看Network。
  • HEAP 内存占用情况
  • 三条虚线:蓝色指DOMConentLoaded,绿线表示第一次绘制,红线表示load事件,很明显看到load是比较慢的。
  • summary loading代表html花的时间,scripting代表脚本的时间,rendering代表计算样式和回流花的时间,painting代表绘制的时间
  • Bottom-up 代表花费排序
  • call-tree 代表调用排序
  • event-log 代表各项事务时间线,重点看看这个,以回流为例子,正好告知前面的回流后一定跟着painting,看看有哪些回流,然后去看看时间节点,发现对应的页面出现。以拼团列表图片高度加载导致的回流问题,可以用一个object-fit演示

如何规避内存泄漏

注意代码规范

垃圾回收

说说垃圾回收

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


kindeditor4.x代码高亮功能默认使用的是prettify插件,prettify是Google提供的一款源代码语法高亮着色器,它提供一种简单的形式来着色HTML页面上的程序代码,实现方式如下: 首先在编辑器里面插入javascript代码: 确定后会在编辑器插入这样的代码: <pre
这一篇我将介绍如何让kindeditor4.x整合SyntaxHighlighter代码高亮,因为SyntaxHighlighter的应用非常广泛,所以将kindeditor默认的prettify替换为SyntaxHighlighter代码高亮插件 上一篇“让kindeditor显示高亮代码”中已经
js如何实现弹出form提交表单?(图文+视频)
js怎么获取复选框选中的值
js如何实现倒计时跳转页面
如何用js控制图片放大缩小
JS怎么获取当前时间戳
JS如何判断对象是否为数组
JS怎么获取图片当前宽高
JS对象如何转为json格式字符串
JS怎么获取图片原始宽高
怎么在click事件中调用多个js函数
js如何往数组中添加新元素
js如何拆分字符串
JS怎么对数组内元素进行求和
JS如何判断屏幕大小
js怎么解析json数据
js如何实时获取浏览器窗口大小
原生JS实现别踩白块小游戏(五)
原生JS实现别踩白块小游戏(一)