我们之前聊过关于浏览器中内存是如何管理、垃圾回收集中常见算法、以及V8引擎中如何做垃圾回收。那么又到了一个老生常谈的话题 -- “道理我都懂”,我怎么知道我写的代码有没有内存问题呢?接下来就给大家推荐一款工具---Performance
为什么使用Performance
GC的目的是为了实现内存空间的良性循环,并且只有合理使用GC才能够达到这种良性循环。那么如何时刻关注GC是不是合理使用了,Performance 就给我们提供了多种监控方式,让我们实时关注我们的内存状态以及GC的工作状态。
如何使用Performance
- 打开浏览器输入目标地址,不跳转
- 进入开发者模式,打开性能
- 开启录制功能,开始访问输入的地址
- 操作页面执行用户行为,一段时间后停止录制
- 生成过程的内存记录信息,分析信息
如何定位内存问题
- 网络正常的情况下,页面出现延迟加载或经常性的暂停(可能因为频繁垃圾回收)
- 页面持续性出现糟糕的性能(可能因为内存膨胀,内存申请过大)
- 页面的性能随时间延长越来越差 (内存泄露)
界定内存问题的标准
- 内存泄露: 内存使用持续升高
- 内存膨胀: 在多数设备上都存在的性能问题,申请值过大,若不同的设备表现一致,则因程序问题
- 频繁垃圾回收: 通过内存变化图进行分析
监控内存的方式
- 浏览器任务管理器
- Timeline 时序图记录
- 堆快照查找分离DOM
- 其他功能判断是否存在频繁的垃圾回收
任务管理器监控内存
任务管理器中调取网页的javascript内存,观察内存是否在一直上涨,如果一直升高,那么证明内存管理存在问题,但是无法判断是什么导致的该问题
Timeline 记录内存
我们用一段代码来模拟一下创建大量dom节点以及创建长度超长的字符串对内存的影响,以及我们如何去使用timeline观测这部分内存变化
<body>
<button id="btn">Add</button>
<script>
// 模拟创建大量dom节点
const btn = document.getElementById("btn")
btn.onclick = () => {
test()
}
const arrList = []
function test() {
for (let i = 0;i< 100000; i++) {
document.body.appendChild(document.createElement("p"))
}
arrList.push(new Array(1000000).join("x"))
}
</script>
</body>
我们使用Performance,采用之前提到过的方法到了一个监控的记录如下:
我们可以从这部分中观察到指定时间段的内存变化以及此时页面所处的状态便于我们去排查问题,另外我们也可以从底部的记录里面观察到js堆、文档、node 节点等等诸多相关变化信息,以助于我们去理解内存变化以及GC策略的执行。
堆快照查找分离DOM
- 界面元素存活在DOM树上
- 垃圾对象的DOM节点(从DOM树上脱离,js代码也不在引用)
- 分离状态的DOM节点(从DOM树上脱离,但js代码还在引用)
如何拍摄堆快照
点击如图所示的按钮,就会按照拍摄一张堆快照放在左侧的列表
我们依然模拟一下使用场景
<body>
<button id="btn">Add</button>
<script>
let ulTem
function test() {
let ul = document.createElement("ul")
for (let i = 0;i< 10; i++) {
let li = document.createElement("li")
ul.appendChild(li)
}
ulTem = ul
}
document.getElementById("btn").addEventListener("click", test)
</script>
</body>
代码中我们可以看到ul是没有被渲染到页面上的,但是它被一个变量给引用了,那么就导致他在后续的GC过程中无法被清除。
如何查看堆快照
我们找到程序执行后的代码发现,的确有ul 以及 li没有被清理。
那么如何去解除他们的引用,很简单 只需要将
ulTem = null
即可
那么以上就是常使用的集中监控方式已经介绍完了。
判断是否存在频繁GC
- GC工作时应用程序是停止的
- 频繁且时间过长的GC常导致应用假死
- 用户使用中感知应用卡顿
所以需要我们去确实是否存在频繁的垃圾回收并且去避免
那么如何确定频繁的垃圾回收?
- Timeline 中频繁的短时间内上升下降
- 任务管理器中数据频繁的迅速增加迅速减小