概述

我们知道,关于GC的工作目的来说,其实就是为了让内存空间在程序运行的过程中,出现一个良性的循环使用。

所谓良性循环的基础其实就是要求开发者在写代码的时候能够对内存空间进行合理的分配。

但是由于ECMAScript当中并没有给程序员提供相应的操作内存空间的API,所以是否合理我们好像也不知道,因为他都是由GC来完成这个操作的。

所以在这里我们如果想去判断整个过程内存使用是否合理,就必须想办法能够时刻关注到当前内存的一个变化。

所以当前就有了这样一款工具可以提供给我们更多的监控方式,来在程序运行过程中帮助我们去完成对内存空间的一个监控操作。

那么这块我们简单总结一下就是通过使用Performance, 我们可以对当前程序运行过程中,内存的变化得到一个实时的监控。

有了这样一个操作之后,我们就可以在程序的内存出现一些问题的时候直接去想办法定位到当前出现问题的这样一个代码快。

那下面我们来看一下Performance工具的基本使用步骤。

首先来说非常简单,我们第一步就是先打开浏览器,在地址栏输入网址。

我们建议使用chrome浏览器,因为本身Performance就是chrome浏览器提供的工具。当然我们其他浏览器也可以有这样的操作,后面具体我们再去说明。

输入完地址之后我们不建议立即进行访问,因为我们想把最初的渲染过程记录下来,所以我们只是打开界面输入网址即可。

然后紧接着我们打开开发人员工具面板(F12),选择性能选项。

进入到性能选项之后,我们需要开启录制功能,开启之后,我们就可以访问目标网址。

然后接着我们充当用户角色,在这个页面上进行一些操作,过一段时间后停止录制。

从而我们就可以得到一个报告,在报告当中就可以去分析那些跟我们当前这样一个内存相关的一些信息了。

有了这样一个步骤之后,我们可以在浏览器中亲自试验一下。

停止录制之后,就会帮我生成当前这个过程中所对应的一些信息,然后在这里面会有一些图表的展示,然后信息也非常的多,所以看起来是比较的麻烦。

不过之前我们也强调了,在这里面呢我们只是关注与内存相关的信息,所以在这里我们看一下会有一个内存的选项(Memory)。默认情况下如果没有勾选我们需要将它勾选。如果勾选了内存我们在页面上就可以看到一个蓝色的线条。

他就属于整个过程当中我们内存所发生的变化,在这里我们也可以根据上面的时序,来看一下有问题的地方。

如果我们当前某个地方有问题的话,可以具体观察,比如有升有降那么就是没问题的,那么这里呢我们就算是对Performance基本做了一个介绍。

为什么我们要去选择这样一个工具,因为我们想要在写代码的过程中去时刻的监控到我们程序运行时整个内存的一个变化,从而呢去发现一些内存的问题,辅助我们在代码当中做一些优化,从而提高我们代码的执行效率。

内存问题的体现

这里我们来看一下,当我们应用程序在执行的过程中,如果内存出现了问题,那么他具体会在我们当前的界面上如何做出展示,那这里我们就可以更好的配合着我们当前Performance工具去进行问题的定位,所以在这里我们就依据相应的性能模型,给定了一些判定的标准。

下面我们就来看一下,当我们的程序的内存出现问题的时候,他具体会表现出什么样的形式。

首先第一条,我们的界面如果出现了延迟加载或者说经常性的暂停,那这里我们首先限定一下网络环境肯定是正常的,所以出现这种情况呢我们一般都会去判定内存是有问题的,而且呢与我们当前这样一个GC存在着频繁的垃圾回收操作是相关的。

具体来说我们如何来利用Performance工具进行观察呢,我们后续会去进行具体的演示。那这里呢我们就大致知道了,如果界面出现了延迟或者经常性的暂停,他的底层可能就伴随着频繁性的垃圾回收,也就是我们的代码中肯定存在瞬间让内存爆炸的代码。那这样的代码一定是不合适的,所以我们需要去进行定位。

第二个就是当我们的界面出现了持续性的糟糕性能表现,也就是说我们在使用过程中,他一直都会让我们觉得不是特别的好用,那这种情况他的底层我们一般会认为存在着内存膨胀。而所谓的内存膨胀指的就是,当前界面为了达到最佳的使用速度,可能会去申请一定的内存空间,但是这个内存空间的大小,远超过了当前设备本身所能提供的大小,所以这个时候就会感知到一段持续性的糟糕性能的体验,同样的我们这块肯定是假设当前网络环境是正常的。

最后,当我们使用一些界面的时候,如果我们感知到这个界面的使用流畅度,随着时间的加长越来越慢,或者说越来越差,那么这个过程呢就伴随着我们内存的泄露,因为在这种情况下我们刚开始的时候是没有问题的,由于我们某些代码的出现,他可能随着时间的一个增长让我们的内存空间越来越少,这也就是所谓的内存泄漏,因此,出现这种情况的时候我们的界面就会随着我们使用时间的增长表现出性能越来越差的一个现象。

那这块呢就是关于我们当前应用程序在执行过程中如果遇到了内存出现问题的情况,他具体在界面上可以呈现给用户的一些信息,那这里我们看到了以后就要去结合我们的Performance来进行一个内存的分析操作,从而去定位到那些有问题的代码,进行修改之后让我们当前的应用程序在执行的过程中会显得更加流畅一些。

监控内存的几种方式

这里我们来看一下几种常见的内存监控方式。

之前我们已经看到了,当我们的内存出现问题的时候我们可以一般归纳为三种情况,第一是内存泄露,第二是内存膨胀,第三就是频繁的垃圾回收。

那么当这些内容出现的时候,我们又该以什么样的标准来进行界定呢,下面我们就去给出一些简单的说明,那这里我们就来看一下这些界定的标准。

第一我们来看一下对于内存泄露来说呢,他其实呢就是内存的一个持续升高,这个呢反而很好判断,因为我们当前已经有很多种方式去获取到我们当前应用程序,执行过程中内存的走势图。

如果我们在这个图上去发现,内存是一直持续升高的,但是整个过程没有下降的这样一个节点,那这也就意味着我们程序代码当中是存在着内存泄露的。所以这个时候我们就应该去代码里面定位一下这样的模块了。

第二个呢是内存膨胀,这个呢就相对的模糊,内存膨胀的本意呢他指的就是当前应用程序本身,为了去达到一个最优的效果,他需要一个很大的内存空间,所以在这个过程当中也许是由于当前设备本身的硬件不支持,那么才造成了我们在使用过程中出现了一些性能上的差异。

所以说如果我们想要去判定, 他当前是程序的问题还是说是我们设备的问题,我们就应该去多做一些测试。

所以说这个时候我们就可以找到那些深受用户喜爱的设备,然后再他们上面都去运行我们当前这样一个应用程序,如果说整个过程中,那么在所有的设备上当前的应用都表现出了很糟糕的一个性能体验。那这就说明我们的程序本身是有问题的,而不是我们的设备有问题。

所以在这种情况下呢,我们就需要去回到我们代码里面,定位到我们当前内存出现问题的地方了。那这款就是我们关于我们如何去界定内存是否存在问题的两个容易说出的标准。

那第三个呢就是我们当前这样一个应用在执行的过程中,如果出现了一个频繁的垃圾回收,那么我们该怎样去界定呢?

这块我们后续就会通过一个内存的变化图来进行分析,因为我们通过界面是没有办法感知到的。

那么现在我们就知道了,如何去界定我们当前的内存是否是达到了一个标准,从来来确定他到底是有没有问题。

那么接下来我们就来看一看具体我们是有哪些方式,来监控我们内存的变化。那这里呢我们主要还是采用浏览器所提供的一些工具。

那么第一个我们所给出的就是浏览器所带的一个叫做任务管理器,这样的一个工具,他可以直接以数值的方式将我们当前应用程序在执行过程中内存的一个变化体现出来。

第二个呢就是我们可以借助于Timeline这样一个时序图,直接把我们当前应用程序执行过程中所有内存的走势以时间点的方式呈现出来,所以有了这张图以后我们就可以很容易的做判断了。这也是很好的监控方式。

再有我们的浏览器中还会有一个叫做堆快照的功能,利用它我们就可以很有针对性的查找我们当前界面对象中是否存在一些分离的DOM, 因为分离DOM的存在也就是一种内存上的泄露。

那么最后至于说我们该去怎样判断这个界面是否存在着频繁的垃圾回收,这就需要我们去借助于不同的工具来获取当前内存的一个走势图,然后进行一个时间段的分析,从而去得出一个判断,那这里就是关于我们当前内存在监控方面我们所要给出的一些说明。

具体来说就两个小点,第一我们要知道自己评判的标准是什么,第二我们要知道如何来对这样的内存进行一个持续性的监控。

任务管理器监控内存

在这之前呢我们都已经知道了,一个web应用在执行的过程中,如果想要观察他内部的一个内存变化,是可以有多种方式的,例如我们这里就通过一段简单的demo来演示一下,我们可以怎样借助浏览器当中自带的任务管理器去监控这个脚本运行时内存的变化。

首先我们来做一个简单的说明,将来我们要去运行文件的平台是浏览器,所以我们编写的是html文件,我们这里在代码当中模拟这样一个内存的变化。

首先我们在界面当中放置一个元素,然后给他添加一个点击事件,当这个事件触发的时候我们就去创建一个长度非常长的一个数组。

那这样呢就会产生一个内存空间上的消耗,我们这里来具体实现一下。

<body>
    <button id="btn">add</button>
    <script>
        const oBtn = document.getElementById('btn');
        oBtn.onclick = function() {
            let arrList = new Array(1000000)
        }
    </script>
</body>

完成之后我们打开浏览器运行,在右上角的“更多”中找到“更多工具”找到“任务管理器” 打开。

这个时候我们就可以在任务管理器中去定位到我们当前正在执行的脚本,默认情况下是没有javascript内存列的,所以如果需要的时候可以直接右击找到javascript内存,展示出来。

接下来我们来看一下如何去分析,首先这里我们最关注的就是内存和js内存这样的两列,那么这两例都叫内存有什么区别。

针对于第一列的内存来说呢,他其实表示的是原生内存,也就是当前界面会有很多DOM节点,而这个内存指的就是DOM节点所占据的内存,如果说这个数值在持续的增大,那就说明我们的界面中在不断的去创建DOM元素,这是第一列内存具有的意思。

那js内存表示的就是js的堆,在这列当中我们需要关注的其实是小括号里面的值,他表示的是我们界面当中所有可达对象正在使用的内存大小,如果说这个数值一直在增大,那就意味着我们当前的界面中要么在创建新对象,要么就是当前现有对象在不断的增长。

比如说以我们写的这个界面为例,我们可以发现当前小括号的值一直是个稳定的数字没有发生变化,那也就意味着我们当前页面是没有内存增长的。

那此时我们可以在这去触发一下click事件(点击按钮),我们这里多点几次,完成以后我们就发现了小括号里面的数值就变大了。

所以通过这样的一个过程我们就可以借助于当前的浏览器任务管理器来监控一下我们脚本运行时整个内存的变化。

那么我们能得出的结论是,如果说我们当前javascript内存列小括号里面的数值一直增大那就意味着我们内存是有问题的。具体来说是什么样的问题,我们当前这个工具是没有办法定位的,他只能发现问题,无法定位问题。

TimeLine记录内容

在之前我们已经可以去使用我们浏览器当中自带的任务管理器来对当前脚本执行中内存的变化去进行监控,但是在使用的过程中我们也可以发现,当前这样的一个操作其实更多的是用于判断当前脚本的内存是否存在问题。

如果我们想要定位这个问题呢,具体和什么样的脚本有关,那么任务管理器就不是那么好用了。所以在这里我们再介绍一个通过时间线记录内存变化的方式来演示一下我们可以怎样更精确的定位到我们当前内存的问题跟哪一块代码是相关的,或者说在什么时间节点上发生的。

那这里我们就通过脚本编写完成。

首先我们放置一个DOM节点,然后添加一个点击事件,首先我们在事件中创建大量的DOM节点来模拟我们内存的消耗,第二我们觉得DOM节点可能不够,我们通过属数组的方式配合着其他的方法形成一个非常非常长的字符串,也去模拟当前大量的内存消耗。

那有了这两个消耗之后我们再回到浏览器当中去看一下,如何使用工具来记录整个过程内存的变化。

那这里我们就开始编写脚本。

因为我们想要利用数组的方式来创建很长的字符串,所以我们先创建一个数组,初始化的时候是空的。

紧接着我们在事件中通过循环创建大量的DOM元素。并且在循环结束之后,我们在数组中添加1000000个x字符串。

<body>
    <button id="btn">add</button>
    <script>
        const oBtn = document.getElementById('btn');

        const arrList = [];

        function test () {
            for (let i = 0; i < 100000; i++) {
                document.body.appendChild(document.createElement('p'))
            }
            arrList.push(new Array(1000000).join('x'))
        }
        oBtn.onclick = test;
    </script>
</body>

到此为止我们的代码就写完了,我们去浏览器当中看一下。

我们先打开浏览器的控制台工具,这里面我们选择性能面板,默认这里是没有运行的,也就是没有记录,我们需要先点击计时操作。

点完以后我们就开始录制了,我们点击几次add按钮,稍等几秒后,点击停止按钮。

完成以后就生成了一个图表,这里密密麻麻的东西我们看起来可能会有些头疼,所以在这里我们关注下我们想要看到的信息就可以了。

见到这样的页面不要慌张,我们之前已经说过,我们这里关注的是内存的变化,所以只要关注内存就好,其他的暂时可以不必理会。我们只要想办法把内存信息提取出来就好。

首先我们先看一下,内存如果没有勾选的话是不会监控内存变化的,我们需要先勾选内存,勾选之后页面上就出现了内存的走势曲线图。

具体来说里面会包含很多信息,他给出来了击中颜色的解释。

当他是蓝色的时候就是js堆,红色就表示当前的文档,绿色就是DOM节点,棕色是监听器,紫色是CPU内存。

这里为了便于观察我们先只保留JS堆,其他的取消勾选,隐藏掉。这样我们就可以很直观的,看到这个脚本运行过程中到目前为止他的一个JS堆的情况走势。

除此以外我们当前这个工具叫时序图,也就是在第一栏,这里是以毫秒为单位,记录了整个页面从空白到渲染结束到最终停状态,这个过程中整个界面的变化。

如果愿意,你可以点进去看一下当前的界面形态,而我们现在关注的只是内存,所以这里我们只看内存的曲线图就可以了。

那么这时候通过这样图表我们简单来分析一下,当这个页面最开始打开的时候其实很长一段时间都是平稳的状态,没有太多的内存消耗。原因在于我们根本没有点击add。

然后紧接着在某一个时间点上突然之间内存就上去了,上去之后是一段平稳的状态,这是因为我们点击了add之后这里的内存肯定是瞬间暴涨的,然后紧接着暴涨之后我们没有任何操作,所以这时候肯定是平稳。

然后紧接着平稳之后又下降了,这就是我们之前所提到的,浏览器本身也是具有垃圾回收机制的,所以当我们的脚本运行稳定之后,那么GC可能在某个时间点上就开始工作了,然后他会发现有一些对象是非活动的,那么他就开始去进行回收,所以一段平稳之后我们这里就降下去了。

降下去之后又会有一些小的浮动,这个属于我们正常的活动开销。

后来我们又有几次连续的点击,这个连续的点击行为呢可能又去造成我们当前内存的飙升,然后不操作之后他又去往下降。

所以通过这样一张内存走势图,我们可以得出的结论就是,当前我们这个脚本里面其实内存还是非常稳定的,因为整个过程呢就是有涨还会有降,而涨呢就是我们去申请内存,降呢就是用完之后我们的GC在正常的去回收我们的内存。

那这里呢就是我们通过浏览器里面的Performance工具他里面有一个timeline的这样一个小的选项,如何去监控我们当前整个内存的变化。

那一旦说我们看到当前这个内存的走势是直线向上走,也就意味着他只有增长而没有回收的操作,那这里必然就存在着内存的一个消耗,所以更有可能是内存泄漏。

我们可以通过上面的时序图去定位问题,当我们发现某一个节点上有问题的时候,那我们可以直接在这里面去定位到那个时间节点,我们可以在时序图上进行拖动查看每一个时间节点上的内存消耗。

然后你还可以看到界面上的一个变化,就可以配合着定位到是哪一块产生了这样一个内存的问题。

所以这里边相对我们的任务管理器来说可能更好用的地方,不但可以帮我们来看一下当前内存是否有问题,而且还可以帮助我们定位这个问题到底在哪个时候发生的,然后再配合当前的界面展示让我们知道我们做了什么样的操作才出现了这样一个问题,从而间接地让我们可以回到代码当中定位这样有问题的一个代码块。

这里就是关于如何利用timeline来记录当前内存的走势,从而监控整个内存的过程。

堆快照查找分离DOM

在这我们去看一下如何去利用浏览器当中所提供的堆快照的功能在我们脚本运行时,进行一些内存的监控操作。

这里我们先来简单的说明一下堆快照功能工作的原理,首先他就是相当于找到我们当前的js堆,然后对她进行一个照片的留存。

有了照片以后我们就可以看到它里面的所有信息,这也就是我们如何去监控的一个由来。

堆快照在使用的时候非常的有用,因为他更像是针对分离DOM的查找行为。

我们都知道在界面上我们看到的很多元素其实都是DOM节点,而这些DOM节点本应该存在于一颗存活的DOM树上的。

不过对DOM节点会有几种形态,一种形态我们称之为垃圾对象,还有一种我们叫分离DOM。

简单的说就是如果这个节点从我们当前的DOM树上进行了脱离,而且在JS代码当中也没有再引用的DOM节点,那么他其实就成为了一个垃圾。

如果说当前的DOM节点只是从DOM树上脱离了,但是在js代码中还有人在饮用者他,我们把这种DOM称为分离DOM。这种分离DOM在界面上是看不见的,但是在内存中是占据着空间的。

所以在这种情况下就是一种内存泄露,因此我们可以通过这样一个堆快照的功能去把他们从这里面都找出来,那么只要能找得到,我们就可以去回到代码里面,针对于这些代码进行一些清除就可以了。从而让我们当前的内存得到一些释放,让我们的脚本在执行的时候也会变得更加迅速。

那说完这些以后我们就去通过脚本把他们实现一下,在这里我们采用和之前一样的思路。

在html里面我们存放一个btn按钮,然后给btn一个点击事件,当我们点击按钮的时候,我们通过js语句去模拟相应的内存变化,比如这次我们要去做的事情就是创建DOM节点。

为了看到更多类型的分离DOM,我们采用ul包裹li的DOM节点创建。我们先在函数中创建,ul节点,然后我们使用循环的方式创建多个li, 将li放在ul里面。

创建之后我们不需要放在页面上,所以也就不插入body中,为了让我们代码引用到这个DOM, 我们使用一个变量tmpEle来指向ul。

<body>
    <button id="btn">add</button>
    <script>
        const oBtn = document.getElementById('btn');

        var tmpEle;

        function fn () {
            var ul = document.createElement('ul');
            for (var i = 0; i < 10; i++) {
                var li = document.createElement('li');
                ul.appendChild(li);
            }
            tmpEle = ul;
        }

        oBtn.addEventListener('click', fn);

    </script>
</body>

代码我们就写完了,简单说明就是我们创建了ul和li节点,但是并没有将他们放在页面中,只是通过js变量引用了这个节点,这就是分离DOM。

我们在浏览器运行一下这个脚本,然后来看如何利用工具来监控这样一个内存变化。我们打开浏览器调试工具,选中内存面板。进入以后我们可以发现这里有一个堆快照的选项。

我们这里做两个行为的测试,第一个呢就是在我们没有点击按钮的情况下,直接去获取当前的快照,这个时候就很迅速的拍了一张照片。

在这个快照里面就是我们当前所动对象的一个具体展示。这里有一个筛选的操作,我们直接检索deta关键字,我们可以发现是没有内容的。

我们回到界面中去做另外一个操作,对当前按钮进行点击,点完以后我们可以再去拍摄一张快照(点击左侧的配置文件文字,出现拍照界面),这次我们还是做和之前一样的操作,检索deta。

这次我们就会发现,快照2里面搜索到了,很明显这几个就是我们自己在代码中所创建的哪几个DOM节点,我们并没有添加到界面中,但是他的确存在于我们的堆中。

这其实就是一种空间上的浪费,因此我们现在就通过堆快照的功能来找到了我们这个脚本里面所存在的问题,也就是存在着所谓的分离DOM。针对这样的问题我们在代码中对使用过后的DOM节点进行清空就可以了。

function fn () {
    var ul = document.createElement('ul');
    for (var i = 0; i < 10; i++) {
        var li = document.createElement('li');
        ul.appendChild(li);
    }
    tmpEle = ul;
    // 清空DOM
    ul = null;
}

在这里我们简单的总结就是,我们可以利用浏览器当中提供的一个叫做堆快照的功能,然后去把我们当前的堆进行拍照,拍照过后我们要找一下这里面是否存在所谓的分离DOM。

因为分离DOM在页面中不体现,在内存中的确存在,所以这个时候他是一种内存的浪费,那么我们要做的就是定位到我们代码里面那些个分离DOM所在的位置,然后去想办法把他给清除掉。

判断是否存在频繁GC

这里我们来说一下如何去确定我们当前web应用在执行过程中是否存在着频繁的垃圾回收,因为我们都已经知道,当GC去工作的时候我们当前的应用程序是停止的。

所以我们当前的GC频繁的工作,而且时间过长,那么这时候对于我们web应用来说就很不友好,因为他会处于一个假死的状态,那么对于用户来说就会感觉到这样一个应用是卡顿的。

所以这个时候我们就要去想办法来确定当前的应用在执行时,是否存在频繁的垃圾回收。

那这里我们给出两种方式,第一种就是我们可以通过timeline时序图的走势来判断一下,我们可以在性能工具面板中对当前的内存走势进行监控。

如果我们发现他那个蓝色的走势条频繁的上升下降。那也就意味着他在频繁的进行垃圾回收。那出现了这样的情况之后我们就必须去定位到相应的时间节点,然后看一下我们具体做了什么样的操作,才造成这样现象的产生,接着我们再代码中进行处理就可以了。

任务管理器在做判断的时候就会显得更加简单一些,因为他就是一个数值的变化,正常来说当我们的界面渲染完成之后,如果没有其他额外的操作,那么无论是我们DOM节点内存,还是我们JavaScript内存来说,他都是一个不变化的数值,或者变化很小。

如果我们这里存在频繁的GC操作时,这个数值的变化就是瞬间增大,瞬间减小,这样的一个节奏,所以我们看到这样的过程也意味着代码存在频繁的垃圾回收操作。

频繁的垃圾回收操作表象上带来的影响是让用户觉得应用在使用的时候会非常卡顿,从内部来说就是当前代码当中存在对内存操作不当的行为让GC不断的去工作,来回收释放相应的空间。

这里就是我们讲到的如何通过内存监控来确定当前应用在执行过程中是否存在着频繁的垃圾回收,当我们确定之后就可以依据相应的一些信息去代码当中进行定位问题,让我们的代码在执行时更加快捷。

总结

在这里我们来看一下Performance工具的总结。

在使用他之前我们首先对Performance工具做了一个介绍,我们知道他是谷歌浏览器所提供的一个性能工具。

那么在这之后我们演示了一下该如何在当前的应用执行过程中去应用Performance工具,至于具体的流程我们就不再赘述了。

然后通过Performance的使用我们可以对内存进行一个适当的监控,然后我们也说了一下内存所能够带来的一些问题,以及如果我们应用程序在执行过程中出现了内存问题之后他会表现成什么样子。

遇到这些展示之后我们可以去把他归结于几类小内存问题,比如内存泄露,内存膨胀,以及频繁的GC操作。

说完这些以后我们就具体的采用了几个小工具对我们程序执行过程中内存的变化进行监控,例如Performance当中的时序图可以记录当前程序执行时内存的走势。

通过蓝色的线条就可以监控到当前内存是如何变化的,从而去定位到当前有问题的时间节点所做的操作,再继续定位代码当中的内容。

除此之外我们还去演示了当前浏览器当中的任务管理器是如何来监控内存变化的,具体来说就是两个数值,一个就是当前DOM节点所占用的内存空间变化,还有就是JS当中的数值变化。

我们在监控的时候如果发现这样的两个数值是持续增加,那就意味着当前代码是不断的有内存申请的,这里我们就要考虑一下是否存在有问题的地方。

在这之后我们又去看到了如何通过堆快照的功能查找代码当中是否存在分离DOM,因为我们都知道,分离的DOM必然存在着内存泄露的现象,如果这样的问题越来越多那么程序就会随着使用时间的增长表现出性能越来越糟糕的情况。

这里呢就是关于Performance工具所涉及到的一些小的总结。