一文带你了解如何排查内存泄漏导致的页面卡顿现象(上)

简介: 不知道在座的各位有没有被问到过这样一个问题:如果页面卡顿,你觉得可能是什么原因造成的?有什么办法锁定原因并解决吗?

这是一个非常宽泛而又有深度的问题,他涉及到很多的页面性能优化问题,我依稀还记得当初面试被问到这个问题时我是这么回答的:


  1. 先会检查是否是网络请求太多,导致数据返回较慢,可以适当做一些缓存


  1. 也有可能是某块资源的bundle太大,可以考虑拆分一下


  1. 然后排查一下js代码,是不是某处有过多循环导致占用主线程时间过长


  1. 浏览器某帧渲染的东西太多,导致的卡顿


  1. 在页面渲染过程中,可能有很多重复的重排重绘


  1. emmmmmm....不知道了


后来了解到了,感官上的长时间运行页面卡顿也有可能是因为内存泄漏引起的


1、内存泄漏的定义


那什么是内存泄漏呢?借助别的大佬给出的定义,内存泄漏就是指由于疏忽或者程序的某些错误造成未能释放已经不再使用的内存的情况。简单来讲就是假设某个变量占用100M的内存,而你又用不到这个变量,但是这个变量没有被手动的回收或自动回收,即仍然占用100M的内存空间,这就是一种内存的浪费,即内存泄漏


2、JS的数据存储


JavaScript的内存空间分为栈内存堆内存,前者用来存放一些简单变量,后者用来存放复杂对象


  • 简单变量指的是JS的基本数据类型,例如:StringNumberBooleannullundefinedSymbolBigInt


  • 复杂对象指的是JS的引用数据类型,例如:ObjectArrayFunction...


3、JS垃圾回收机制


根据内存泄漏的定义,有些变量或数据不再被使用或不需要了,那么它就是垃圾变量或垃圾数据,如果其一直保存在内存中,最终可能会导致内存占用过多的情况。那么此时就需要对这些垃圾数据进行回收,这里引入了垃圾回收机制的概念


垃圾回收的机制分为手动自动两种


例如C/C++采用的就是手动回收的机制,即先用代码为某个变量分配一定的内存,然后在不需要了后,再用代码手动释放掉内存


JavaScript采用的则是自动回收的机制,即我们不需要关心何时为变量分配多大的内存,也不需要关心何时去释放内存,因为这一切都是自动的。但这不表示我们不需要关心内存的管理!!!!否则也不会有本文讨论的内存泄露了


接下来就讲一下JavaScript的垃圾回收机制


通常全局状态(window)下的变量是不会被自动回收的,所以我们来讨论一下局部作用域下的内存回收情况


function fn1 () {
    let a = {
        name: '零一'
    }
    let b = 3
    function fn2() {
        let c = [1, 2, 3]
    }
    fn2()
    return a
}
let res = fn1()


以上代码的调用栈如下图所示:


954cfbad581dd719f6158af44e7b64dc.png


图中左侧为栈空间,用于存放一些执行上下文和基本类型数据;右侧为堆空间,用于存放一些复杂对象数据


当代码执行到fn2()时,栈空间内的执行上下文从上往下依次是 fn2函数执行上下文 => fn1函数执行上下文 => 全局执行上下文


fn2函数内部执行完毕以后,就该退出fn2函数执行上下文了,即箭头向下移动,此时fn2函数执行上下文会被清除并释放栈内存空间,如图所示:、


1d2efa09dfda45468747e1959cf157ef.png


fn1函数内部执行完毕以后,就该退出fn1函数执行上下文了,即箭头再向下移动,此时fn1函数执行上下文会被清除并释放相应的栈内存空间,如图所示:


3c583caf71fddc2e10d818b6e11dc990.png


此时处于全局的执行上下文中。JavaScript的垃圾回收器会每隔一段时间遍历调用栈,假设此时触发了垃圾回收机制,当遍历调用栈时发现变量b和变量c没有被任何变量所引用,所以认定它们是垃圾数据并给它们打上标记。因为fn1函数执行完后将变量a返回了出去,并存储在全局变量res中,所以认定其为活动数据并打上相应标记。待空闲时刻就会将标记上垃圾数据的变量给全部清除掉,释放相应的内存,如图所示:


08ab76e4561c17e994be2694e018edde.png


从这我们得出几点结论:


  1. JavaScript的垃圾回收机制是自动执行的,并且会通过标记来识别并清除垃圾数据


  1. 在离开局部作用域后,若该作用域内的变量没有被外部作用域所引用,则在后续会被清除


补充:JavaScript的垃圾回收机制有着很多的步骤,上述只讲到了标记-清除,其实还有其它的过程,这里简单介绍一下就不展开讨论了。例如:标记-整理,在清空部分垃圾数据后释放了一定的内存空间后会可能会留下大面积的不连续内存片段,导致后续可能无法为某些对象分配连续内存,此时需要整理一下内存空间;交替执行,因为JavaScript是运行在主线程上的,所以执行垃圾回收机制时会暂停js的运行,若垃圾回收执行时间过长,则会给用户带来明显的卡顿现象,所以垃圾回收机制会被分成一个个的小任务,穿插在js任务之中,即交替执行,尽可能得保证不会带来明显的卡顿感


4、Chrome devTools查看内存情况


在了解一些常见的内存泄漏的场景之前,先简单介绍一下如何使用Chrome的开发者工具来查看js内存情况


首先打开Chrome的无痕模式,这样做的目的是为了屏蔽掉Chrome插件对我们之后测试内存占用情况的影响


df69d24283f0f5247f4829ea5edc749c.jpg


然后打开开发者工具,找到Performance这一栏,可以看到其内部带着一些功能按钮,例如:开始录制按钮;刷新页面按钮;清空记录按钮;记录并可视化js内存、节点、事件监听器按钮;触发垃圾回收机制按钮等等


f8ba76de8e5644d22dc3e763f8ed0512.jpg


简单录制一下百度页面,看看我们能获得什么,如下动图所示:


0b4419a5420c1941fc2b7541b3029bac.jpg


从上图中我们可以看到,在页面从零到加载完成这个过程中JS Heap(js堆内存)documents(文档)Nodes(DOM节点)Listeners(监听器)GPU memory(GPU内存)的最低值、最高值以及随时间的走势曲线,这也是我们主要关注的点


再来看看开发者工具中的Memory一栏,其主要是用于记录页面堆内存的具体情况以及js堆内存随加载时间线动态的分配情况


97dff4b5dc51f12c8d90a4b47f39e7bc.jpg


堆快照就像照相机一样,能记录你当前页面的堆内存情况,每快照一次就会产生一条快照记录,如图所示:


f64a8138120b84db3059972efa11c908.jpg


如上图所示,刚开始执行了一次快照,记录了当时堆内存空间占用为13.9MB,然后我们点击了页面中某些按钮,又执行一次快照,记录了当时堆内存空间占用为13.4MB。并且点击对应的快照记录,能看到当时所有内存中的变量情况(结构、占总占用内存的百分比...)


然后我们还可以看一下页面动态的内存变化情况,如图所示:


c275557c98b773e92db6af22f926de93.jpg


在开始记录后,我们可以看到图中右上角有起伏的蓝色与灰色的柱形图,其中蓝色表示当前时间线下占用着的内存;灰色表示之前占用的内存空间已被清除释放。


从上图过程来看,我们可以看到刚开始处于的tab所对应显示的页面中占用了一定的堆内存空间,成蓝色柱形,在点击别的tab后,原tab对应的内容消失,并且原来蓝色的柱形变成灰色(表示原占用的内存空间得到了释放),同时新tab所对应显示的页面也占用了一定的堆内存空间。因此后续我们就可以针对这个图来查看内存的占用与清除情况

相关文章
|
Java 数据库连接
Java中的内存泄漏排查与预防方法
Java中的内存泄漏排查与预防方法
|
监控 Java
Java中的内存泄漏分析与排查技巧
Java中的内存泄漏分析与排查技巧
|
存储 监控 算法
LeakCanary 的内存泄露问题排查
LeakCanary 的内存泄露问题排查
149 0
|
10月前
|
算法
虚拟内存的页面置换算法有哪些?
【10月更文挑战第25天】不同的页面置换算法各有优缺点,在实际应用中,操作系统会根据不同的应用场景和系统需求选择合适的页面置换算法,或者对算法进行适当的改进和优化,以平衡系统的性能、开销和资源利用率等因素。
272 5
|
缓存 算法 安全
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(二)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
135 0
|
12月前
|
监控 Java Linux
redisson内存泄漏问题排查
【9月更文挑战第22天】在排查 Redisson 内存泄漏问题时,首先需确认内存泄漏的存在,使用专业工具(如 JProfiler)分析内存使用情况,检查对象实例数量及引用关系。其次,检查 Redisson 使用方式,确保正确释放资源、避免长时间持有引用、检查订阅和监听器。此外,还需检查应用程序其他部分是否存在内存泄漏源或循环引用等问题,并考虑更新 Redisson 到最新版本以修复潜在问题。
383 5
|
JavaScript Java 开发工具
Electron V8排查问题之接近堆内存限制的处理如何解决
Electron V8排查问题之接近堆内存限制的处理如何解决
679 1
|
算法 程序员
理解操作系统内存管理:页面置换算法全解析
大家好,我是小米,热爱分享技术的大哥哥!今天聊的是操作系统中的页面置换算法。它解决的是内存满载时,如何选择合适的页面移出以腾出空间的问题。主要有三种算法:FIFO(先进先出),简单但性能不佳;LRU(最近最久未使用),考虑时间局部性,性能较好但实现较复杂;OPT(最佳置换),理论上最优但无法实际应用。这些算法各有千秋,在实际应用中需根据场景选择最合适的方案。希望这能帮大家更好地理解内存管理的核心机制!
405 2
|
监控 安全 Java
JVM内存问题之排查Direct Memory泄漏有哪些常用方法
JVM内存问题之排查Direct Memory泄漏有哪些常用方法
458 2
|
监控 Java Linux
JVM内存问题之如果堆内存一直缓慢上涨,如何解决
JVM内存问题之如果堆内存一直缓慢上涨,如何解决
1258 1