当前位置: 首页 > 科技观察

内存溢出分析及解决实践

时间:2023-03-20 15:27:50 科技观察

如果页面卡顿,或者半夜内存飙升,你认为可能是什么原因造成的?有什么方法可以锁定原因并解决吗?(项目中常用)这是一个非常广泛和深入的问题,涉及到很多页面性能优化的问题。依稀记得面试中被问到这个问题时的回答:1)首先会检查是否是网络请求过多,导致数据返回慢。你可以适当的做一些缓存;2)也有可能是某个资源的bundle太大,可以考虑拆分;3)然后查看js代码,看看是不是某处循环过多,占用主线程时间过长;4)浏览器某帧渲染的东西太多,导致卡顿;5)在页面渲染过程中,可能会有多次重复的重排和重绘;后来了解到,感官长时间运行的页面冻结也可能是由于内存泄漏,内存溢出引起的。一、内存溢出和内存泄漏1、内存溢出1)程序运行时出现的错误。2)当程序运行所需内存超过计算机剩余内存时,程序崩溃并抛出内存溢出错误。例如:系统不能再分配你需要的空间。比如你需要100M的空间,系统只剩下90M了。这称为内存溢出。2、内存泄漏1)表示内存被占用,没有及时释放。2)内存泄漏越多,计算机剩余内存越小,此时越容易出现内存溢出。常见的内存泄漏:1)意外的全局变量2)定时器或回调函数没有及时清理3)关闭例如:当你使用一个资源时,为它开辟了一个空间,用完了却忘记释放资源.当内存还被占用的时候,一次无所谓,但是如果内存泄漏太多,就会导致内存溢出。内存泄漏是导致应用程序运行缓慢、崩溃甚至其他问题的根本原因。2.垃圾回收机制垃圾回收机制(GC)根据内存泄漏的定义,一些变量或数据不再使用或不需要,那么它就是垃圾变量或垃圾数据。如果一直保存在内存中,最终可能会泄漏导致内存占用过多。那么此时就需要对这些垃圾数据进行回收,这里就引入了垃圾回收机制的概念。一、GC的定义及作用GC是垃圾回收机制的缩写;GC可以发现内存中的垃圾,并释放和回收空间;2、GC中什么是垃圾1)对象functionfunc(){程序中不再需要name='lg'return`${name}isacoder`}func()2)不再需要使用对象程序中functionfunc(){constname='lg'return`${name}isacoder`}func()3.常用GC算法——引用计数引用计数算法实现原理引用计数算法(重要)核心思想:设置references判断当前引用数是否为0;参考计数器;引用关系发生变化时修改引用编号;引用数为0时立即回收;constuser1={age:11}constuser2={age:22}constuser3={age:33}constnameList=[user1.age,user2.age,user3.age]functionfn(){constnum1=1;constnum2=2;}fn();注意:fn()的num1和num2会被回收。4.常用GC算法——MarkClearMarkClear算法实现原理核心思想:分两个阶段完成:mark和clear;遍历所有对象,找到标记的活动对象;遍历所有对象,清除未标记的对象;回收相应的空间;functionspeakLines(){letnight="It'sdark";//标记进入环境letcloseEyes="closeeyes";//标记进入环境letspeak=`开始狼人杀,${night}please${closeEyes}`;//做标记,进入环境console.log(speak);}speakLines()//代码中标记的变量执行,标记离开环境,最后回收。3、V8引擎-节点内存溢出用户反馈说是半夜没人用的时候,内存飙升,分析节点内存溢出问题。1.初步调查(重要)初步调查内容如下:1)日志:数据分析大小,内存溢出日志。2)监控:开启哨兵监控、Sonar代码质量、阿里云监控。1)错误日志数据分析B.错误日志2021-05-1322:26:48:198S:FATALERROR:CALL_AND_RETRY_LASTAllocationfailed-JavaScriptheapoutofmemory2021-05-1500:18:33:670S:FATALERROR:Inemeffectivemark-compactsnearheaplimited-Allocationfailed(重要)1)V8引擎有内存限制Node.js(和ChromeV8)的大小。2)代码分析:内存泄漏(重要)redis服务(未使用,移除);无用的节点安装依赖项;未启用节点回收机制;网络套接字机制;节点中的套接字是否被销毁;api是否存储在node端返回的数据(导致内存溢出);节点代理时是否发送到dns,还是带域名的地址(网速慢导致代理失败);3.Nodejs内存——扩展容量V8引擎对Node.js(和ChromeV8)内存限制的大小。原因是在Node中,通过JavaScript使用内存时只能使用部分内存(64位系统:1.4GB,32位系统:0.7GB)。这时候,如果前端项目非常大,Webpack在编译时会占用大量内存,如果系统资源超过了V8引擎对Node默认的内存限制,就会出现内存溢出错误。方案一:通过package.json中的build增加内存"scripts":{"dev":"nodebuild/dev-server.js","start":"nodebuild/dev-server.js","build":"setNODE_ENV=production&&node--max_old_space_size=4096build/build.js","e2e":"nodetest/e2e/runner.js","test":"npmrune2e"},方案二:安装插件,增大大小内存第一步:在packagejson中添加npminstall-gincrease-memory-limitnpminstallcross-env这两个插件第二步:在package.json中的scripts中配置"scripts":{"fix-memory-limit":"cross-envLIMIT=8192increase-memory-limit"},LIMIT是你要分配的内存大小,这里的8192单位是M,也就是8G(具体大小根据实际情况而定);第三步:npmrunfix-memory-limit*注意:增加内存限制大小并不能真正解决问题。扩展知识1.理解V8V8是主流的JavaScript执行引擎。V8使用即时编译。V8有内存限制,因为在Node中,通过JavaScript使用内存时,只能使用部分内存(64位系统:1.4GB,32位系统:0.7GB)2.V8垃圾回收策略采用概念世代复苏。内存分为新生代和老年代。不同的算法用于不同的对象。3、V8如何回收新生代对象?用于存放新生代对象(32M|16M)新生代是指存活时间短的对象V8内存分配新生代对象回收实现回收过程采用复制算法+标记整理新生代内存区域分为两部分equal-sizedspaces使用的空间是From,可用空间是To。活动对象存储在From空间中。标记和排序后,将活动对象复制到ToFrom和To交换空间。释放和回收细节。复制过程中可能会出现推广说明。提升就是将新生代的对象移动到老年代进行一轮GC,存活的新生代需要提升。To空间的利用率超过25%。4、V8中如何回收老年代对象。老年代对象存放在右边的老年代区域。64位操作系统1.4G,32位操作系统700MOldgenerationObject指的是存活时间较长的对象。老年代对象回收主要采用标记清除、标记排序、增量标记算法实现。首先,标记清除用于完成垃圾空间回收。标记排序用于空间优化。增量标记用于效率优化。详细对比新生代区垃圾回收以空间换时间。老年代区的垃圾回收不适合复制算法标记增量。如何优化垃圾回收。Heapsnapshotsearch和分离DOM判断是否频繁垃圾回收(低版本对应性能),chromememory(低版本版本是chromeprofiles,主要使用JSheapsnapshots,JSheapdynamicallocationtimeline)。1)使用Chrome任务管理器查看网页使用了多少内存。2)使用时间线记录和可视化内存使用情况。3)使用堆快照来识别分离的DOM树(内存泄漏的常见原因)。4)通过堆动态分配时间轴记录了解JS堆中的分配和回收。首先,打开Chrome的隐身模式。这样做的目的是为了阻断Chrome插件对我们后续测试内存占用的影响。然后打开开发者工具,找到性能栏,可以看到里面有一些功能按钮,比如:开始录制按钮;刷新页面按钮;清除记录按钮;记录并可视化js内存、节点、事件监听器按钮;触发垃圾回收机制按钮等。简单记录一下百度页面,看看我们能得到什么,如下动图所示:从上图我们可以看到,在页面从零加载到完成的过程中,JSHeap(js堆内存),文档(documents)、Nodes(DOM节点)、Listeners(监听器)、GPU内存(GPUmemory)的最小值、最大值和随时间变化的趋势曲线,这是我们主要关注的。再来看看开发者工具中的Memory栏,主要用来记录页面堆内存的具体情况和js堆内存随加载时间线的动态分配五、代码优化代码优化简介(important)JavaScript中的内存管理自动补全;执行引擎会使用不同的GC算法;算法工作的目的是实现内存空间的良性循环;性能工具监控内存变化;JavaScript是一种单线程机制的解释型语言;递归导致内存溢出(函数执行时再次调用自己执行)//下面的情况是“死递归”UncaughtRangeError:Maximumcallstacksizeexceeded"memoryoverflow"functionfn(x){//console.log(x);fn(x+1);}fn(1);虽然JavaScript会自动进行垃圾回收,但是如果我们的代码没有及时清除一些被调用的东西,就会让变量一直处于不可回收的状态。globalvariablescausefunctionleaks(){leak='xxxxxx';//leak成为全局变量,不会被回收}Closurevarleaks=(function(){varleak='xxxxxx';//被闭包引用,不会被回收returnfunction(){console.log(leak);}})()忘记定时器varsomeResource=getData();setInterval(function(){varnode=document.getElementById('Node');if(node){//DostuffwithnodeandsomeResource.node.innerHTML=JSON.stringify(someResource));}},1000);eval使用eval()函数会带来安全隐患。eval()函数的作用是返回任意字符串。何时对js代码进行处理。原代码:this.formColor=eval('('+resp.responsePageData.sysColor+')');处理后代码:this.formColor=JSON.parse(resp.responsePageData.sysColor);*小结在项目过程中,如果遇到一些可能与内存泄漏相关的性能问题,可以参考本文的方法进行排查,一定能够找到问题并给出解决方案。虽然JavaScript垃圾回收是自动的,但我们有时需要考虑是否手动清除某些变量占用的内存。例如,你知道某个变量在某些情况下不再需要,但它会被外部变量引用。当内存无法释放时,可以为变量重新赋值为null,以在后续的垃圾回收阶段释放该变量的内存。