谷歌devtools官网 https://developers.google.com/web/tools/chrome-devtools
使用 Chrome 发现内存泄漏
<body>
<!-- 1. 一直添加DOM导致内存泄漏 -->
<div id="nodes"></div>
<script>
var x = []; // 2. 全局变量 不会被回收
function createSomeNodes() {
var div,
i = 100,
frag = document.createDocumentFragment(); // 创建文档片段用于内存中
for (; i > 0; i--) {
div = document.createElement("div");
div.appendChild(document.createTextNode(i + " - " + new Date().toTimeString()));
frag.appendChild(div);
}
document.getElementById("nodes").appendChild(frag);
}
function grow() {
x.push(new Array(1000000).join('x')); // 创建字符串
// 这里的内存还是在string中,只是数组中的k指向了这里
createSomeNodes();
setTimeout(grow, 1000);
}
grow()
</script>
找出周期性增长的内存
performance 标签擅长做这些。在 Chrome 中打开例子,打开 Dev Tools ,切换到 performance ,勾选 memory 并点击记录按钮,然后点击页面上的 The Button
按钮。过一阵停止记录看结果:
从HEAP图可以看出
两种迹象显示出现了内存泄漏,图中的 Nodes(绿线)和 JS heap(蓝线)。Nodes 稳定增长,并未下降,这是个显著的信号。
JS heap 的内存占用也是稳定增长。由于垃圾收集器的影响,并不那么容易发现。图中显示内存占用忽涨忽跌,实际上每一次下跌之后,JS heap 的大小都比原先大了。换言之,尽管垃圾收集器不断的收集内存,内存还是周期性的泄漏了。
保存两个快照
切换到 Chrome Dev Tools 的 memory 标签,点击Heap Snapshot 保存快照作为基准。而后再次点击 The Button
按钮,等数秒以后,保存第二个快照。
筛选菜单选择 Comparison ,然后可以看到一个对比列表。
可以找到HTMLDivElement 和 Text 和 (Contented string)三个指标是明显增加了的说明是有泄漏的地方
Record heap allocations 找内存泄漏
切换到 Chrome Dev Tools 的 memory 标签,点击第二项
工具运行的时候,注意顶部的蓝条,代表了内存分配,每一秒有大量的内存分配。运行几秒以后停止。
上图中可以看到工具的杀手锏:选择某一条时间线,可以看到这个时间段的内存分配情况。尽可能选择接近峰值的时间线,下面的列表仅显示了三种 constructor:其一是泄漏最严重的(string)
(现在为
Contented string)
,下一个是关联的 DOM 分配,最后一个是 Text
constructor(DOM 叶子节点包含的文本),可以看到HTMLDivElement中的内容一直在嵌套无法被 GC 回收
切换到allocation Stack现在知道元素被分配到哪里了吧(grow
-> createSomeNodes
)
Chrome中内置插件使用
页面重绘区域
ctrl + shift + p 打开后输入show Rendering
在勾选Paint flashing,之后页面发生重绘或者回流(因为回流一定会重绘)就会有提示
当然其实勾选其他的选项还是有其他的功能的
CPU的性能监控
选择>show performance monitor插件
页面渲染常见指标
- DCL: DOMContentLoaded Event 浏览器首先加载解析 HTML,HTML 加载解析完成后,触发 DOMContentLoaded Event。此时浏览器尚未开始渲染,其他静态资源,比如图片等也还没加载
- FP First Paint 页面开始渲染HTML
- FCP: First Contentful Paint 有第一个HTML内容
- FMP: First Meaningful Paint 系统判断出现大量DOM的时候触发
- LCP: Largest Contentful Paint 全部HTML加载完成
- L: Onload Event 浏览器把图片加载回来了,所有资源都加载完成,
- TTI:Time to Interractive 可交互时间
关键性能指标
案例
测试性能API
window.addEventListener('load', (event) => {
// Time to Interactive
let timing = performance.getEntriesByType('navigation')[0];
console.log(timing.domInteractive);
console.log(timing.fetchStart);
let diff = timing.domInteractive - timing.fetchStart;
console.log("TTI: " + diff);
})
DNS 解析耗时: domainLookupEnd - domainLookupStart
TCP 连接耗时: connectEnd - connectStart
SSL 安全连接耗时: connectEnd - secureConnectionStart
网络请求耗时 (TTFB): responseStart - requestStart
数据传输耗时: responseEnd - responseStart
DOM 解析耗时: domInteractive - responseEnd
资源加载耗时: loadEventStart - domContentLoadedEventEnd
First Byte时间: responseStart - domainLookupStart
白屏时间: responseEnd - fetchStart
首次可交互时间: domInteractive - fetchStart
DOM Ready 时间: domContentLoadEventEnd - fetchStart
页面完全加载时间: loadEventStart - fetchStart
http 头部大小: transferSize - encodedBodySize
重定向次数:performance.navigation.redirectCount
重定向耗时: redirectEnd - redirectStart
资源所需时间
Queuing,也就是排队的意思,当浏览器发起一个请求的时候,会有很多原因导致该请求不能被立即执行,而是需要排队等待。导致请求处于排队状态的原因有很多。
- 首先,页面中的资源是有优先级的,比如 CSS、HTML、JavaScript 等都是页面中的核心文件,所以优先级最高;而图片、视频、音频这类资源就不是核心资源,优先级就比较低。通常当后者遇到前者时,就需要“让路”,进入待排队状态。
- 其次,我们前面也提到过,浏览器会为每个域名最多维护 6 个 TCP 连接,如果发起一个 HTTP 请求时,这 6 个 TCP 连接都处于忙碌状态,那么这个请求就会处于排队状态。
- 最后,网络进程在为数据分配磁盘空间时,新的 HTTP 请求也需要短暂地等待磁盘分配结束。
Stalled, 等待排队完成之后,就要进入发起连接的状态了。不过在发起连接之前,还有一些原因(判断是否有缓存)可能导致连接过程被推迟,这个推迟就表现在面板中的Stalled上,它表示停滞的意思。
Proxy Negotiation, 如果你使用了代理服务器,还会增加一个Proxy Negotiation阶段,也就是代理协商阶段,它表示代理服务器连接协商所用的时间,不过在上图中没有体现出来,因为这里我们没有使用代理服务器。
Initial connection/SSL 阶段,也就是和服务器建立连接的阶段,这包括了建立 TCP 连接所花费的时间;不过如果你使用了 HTTPS 协议,那么还需要一个额外的 SSL 握手时间,这个过程主要是用来协商一些加密信息的。
Request sent 阶段。通常这个阶段非常快,因为只需要把浏览器缓冲区的数据发送出去就结束了,并不需要判断服务器是否接收到了,所以这个时间通常不到 1 毫秒。
Waiting (TTFB),数据发送出去了,接下来就是等待浏览器接收服务器第一个字节的数据,通常也称为“第一字节时间”。 TTFB 是反映服务端响应速度的重要指标,对服务器来说,TTFB 时间越短,就说明服务器响应越快。
Content Download 阶段,这意味着从第一字节时间到接收到全部响应数据所用的时间。
优化时间线上耗时项
1. 排队(Queuing)时间过久
排队时间过久,大概率是由浏览器为每个域名最多维护 6 个连接导致的。那么基于这个原因,你就可以让 1 个站点下面的资源放在多个域名下面,比如放到 3 个域名下面,这样就可以同时支持 18 个连接了,这种方案称为域名分片技术。除了域名分片技术外,我个人还建议你把站点升级到 HTTP2,因为 HTTP2 已经没有每个域名最多维护 6 个 TCP 连接的限制了。
2. 第一字节时间(TTFB)时间过久
这可能的原因有如下:
- 服务器生成页面数据的时间过久。对于动态网页来说,服务器收到用户打开一个页面的请求时,首先要从数据库中读取该页面需要的数据,然后把这些数据传入到模板中,模板渲染后,再返回给用户。服务器在处理这个数据的过程中,可能某个环节会出问题。
- 网络的原因。比如使用了低带宽的服务器,或者本来用的是电信的服务器,可联通的网络用户要来访问你的服务器,这样也会拖慢网速。
- 发送请求头时带上了多余的用户信息。比如一些不必要的 Cookie 信息,服务器接收到这些 Cookie 信息之后可能需要对每一项都做处理,这样就加大了服务器的处理时长。
对于这三种问题,你要有针对性地出一些解决方案。面对第一种服务器的问题,你可以想办法去提高服务器的处理速度,比如通过增加各种缓存的技术;针对第二种网络问题,你可以使用 CDN 来缓存一些静态文件;至于第三种,你在发送请求时就去尽可能地减少一些不必要的 Cookie 数据信息。
3. Content Download 时间过久
如果单个请求的 Content Download 花费了大量时间,有可能是字节数太多的原因导致的。这时候你就需要减少文件大小,比如压缩、去掉源码中不必要的注释等方法。
强制回流在Google浏览器上的体现
RAIL测量模型
- Response 响应 一些操作的响应时间 处理事件必须在50ms-100ms内完成
- Animation动画 每10ms 产生一帧 60fps
- Idle 空闲 尽可能增加空闲时间 (防止主线CPU处理程拥挤),一些不必要在前端算的东西不要放在前端,(由于重绘和回流是在主线程,所以说少点)
- Load 加载 资源加载的时间 在5S内完成加载并且可以交互 (需要考虑网络差的情况)
WebPageTest工具使用
不同地区,不同浏览器,自己设置不同的网速,测试的次数,是否是第一次访问,还是有缓存后的访问(First View; Repeat View)
WebPageTest 可以本地部署
网站
测试完成后的基础指标