前言
JS内存化的原理是以参数为键,函数结果为值,缓存对象,为CPU执行事件交换内存空间。memoization的潜在陷阱是严格缓存有一个完美的过期策略,而普通对象的键值对没有。
函数执行后,用闭包缓存的对象的内存空间不会被清空,会造成内存占用,在执行量大、参数多样的情况下不会被释放。
因此,本文将讨论JS垃圾收集。
JS垃圾收集机制的基本原理是:
找出不再使用的变量,然后释放它们占用的内存。垃圾收集器将以固定的时间间隔定期执行此操作。
那么我们如何知道变量是否还在使用呢?
首先,局部变量的生命周期处于函数声明和执行阶段。函数执行后,局部变量是不必要的。在浏览器关闭或进程关闭之前,不能释放全局变量。
但也有一些场景,比如闭包,通过作用域链访问函数外的自由变量,使自由变量保存在内存中,不会随着函数的执行而结束,对象的相互引用等。所以垃圾收集器判断哪些变量有用,哪些变量没用就没那么容易了。
//经典闭包函数closure(){ var name=' inner name ';return function(){ console . log(name);}}var inner=闭包();inner();//innerName;因此,识别无用变量的策略可能实现方式不同,但目前浏览器中通常有两种策略:标签清除和引用计数。
第二,标记-扫描(标记-扫描)
从2012年开始,所有现代浏览器都使用了Mark-Clear垃圾收集算法。什么是清除标记?
当一个变量进入执行环境时,它被标记为“进入环境”。当变量离开环境时,它被标记为“离开环境”。从逻辑上讲,进入环境的变量所占用的内存是永远无法释放的,因为只要执行流进入相应的环境,它们就可能被使用。
当垃圾收集器运行时,它将标记存储在内存中的所有变量。
然后,它删除环境中的变量和环境中的变量引用的标签。之后,标记的变量将被视为要删除的变量,因为环境中的变量不再能够访问这些变量。
最后,垃圾收集器完成内存清理工作,销毁那些标记的值,并回收它们占用的内存空间。
此外,标记-清除还有一个问题,即清除后内存空间不连续,即出现内存碎片。如果以后需要更大的连续内存空间,将达不到要求。Mark-Compact方法可以有效地解决这个问题。标记阶段也没什么不同,只是标记后,标记-排序的方法会将活体移动到内存的一端,最后清理边界处的内存。
第三,参考计数
另一种不太常见的垃圾收集策略叫做引用计数(Reference Counting),它将“一个对象是否不再需要”的定义简化为“一个对象是否有其他对象引用它”。如果没有引用指向该对象(零引用),该对象将被垃圾收集机制回收。引用计数的策略是跟踪每个值被使用了多少次。当一个变量被声明并且一个引用类型被分配给它时,这个值的引用次数将增加1。如果这个变量的值变成另一个,这个值的参考次数将减少1。当该值的引用次数变为0时,意味着没有变量在使用,无法访问该值,因此可以回收其占用的空间,这样垃圾收集器在运行时会清理引用次数为0的值。但是引用计数不再使用了,因为循环引用的问题会导致内存泄漏。
函数问题(){ var objA=new Object();var objB=new Object();objA.someObject=objBobjB.anotherObject=objA}objA和objB通过各自的属性相互引用,即两个对象的引用次数都是2。函数执行后,objA、objB将继续存在,因为它们的引用计数永远不会为0。如果多次执行此功能,将不会释放大量内存。
4.NodeJs V8中的垃圾收集机制
在Node中,通过JS使用内存时,发现只能使用部分内存(64位系统约1.4 GB,32位系统约0.7 GB),导致Node无法直接操作大内存对象。
这是因为,以1.5GB的垃圾收集堆内存为例,V8做一个小的垃圾收集需要50毫秒以上,做一个非增量的垃圾收集需要1秒以上,垃圾收集过程会导致JS线程暂停执行这么长时间。所以在当时的考虑下,直接限制堆内存是一个不错的选择。
那么,在这样的内存限制下,V8垃圾收集机制有什么特点呢?
4.1.内存生成算法
V8的垃圾收集策略主要基于世代垃圾收集机制。在V8中,内存分为新一代和老一代。新生代的对象是生存时间短的,老一代的对象是生存事件长或记忆常驻的。
V8堆的整体大小等于新一代使用的内存空间加上老一代的内存空间,只能在启动时指定,这意味着在运行时无法自动扩展。如果超过极限值,将导致过程错误。
4.2清除算法
在生成的基础上,新一代中的对象主要通过清除算法进行回收。在扫码的具体实现中,主要采用了一种复制方法,即——切尼算法。
切尼算法将堆内存分成两部分,一部分在使用中称为From space,另一部分在空闲时称为to space。分配对象时,首先在“从”空间中分配对象。
当垃圾收集开始时,将检查“从”空间中幸存的对象,并将其复制到“到”空间,同时释放非幸存对象占用的空间。复制后,“从空间”和“到空间”的角色互换。
当一个对象经过反复复制存活下来后,会被认为是一个生命周期较长的对象,然后会移动到老一代,由新的算法进行管理。
在另一种情况下,如果一个对象被复制到“到”空间,并且“到”空间占用超过25%,则该对象将被直接提升到旧的代空间。
4.3标记-清除和标记-完成算法
对于老一代的对象,主要使用标记清除和标记排序算法。清除标记与上面提到的标记相同。与清除算法相比,标记清除不会将内存空间分成两半。Mark-clear将在标记阶段标记活动对象,而它将在内存回收阶段清除未标记的对象。标签排序的目的是解决标签清除后内存碎片的问题。
4.4增量标记算法
在前面三种算法中,需要暂停正在执行的JavaScript应用程序逻辑,并在垃圾收集后恢复。这种行为被称为“停止世界”。
在V8新生代的世代恢复中,只采集了新生代,而新生代通常分配较小,幸存物较少,因此总停顿的影响不显著,而老世代则相反。
为了减少所有老一代垃圾收集造成的暂停时间,V8将标记过程划分为子标记过程,并使垃圾收集标记和JS应用逻辑交替进行,直到标记阶段完成。
增量标记改进后,垃圾收集的最大暂停时间可以减少到原来的1/6左右。
动词(verb的缩写)内存泄漏
内存泄漏是指程序中已经动态分配的堆内存由于某种原因尚未释放或无法释放,造成系统内存的浪费,导致程序运行缓慢甚至系统崩溃等严重后果。
第六,内存泄漏的常见场景
6.1缓存
正如文章前言中提到的,JS开发人员喜欢用对象的键值对来缓存函数的计算结果,但是缓存中存储的键越多,存活时间就越长的对象就越多,这会导致垃圾收集在扫描和排序时对这些对象做无用的工作。
6.2未发布的范围(关闭)
var LeakArray=[];exports . leak=function(){ leak array . push(' leak ' math . random());}在上述代码中,模块编译执行后形成的作用域不会因为模块缓存而被释放。每次调用LEAKARY方法,局部变量LEAKARY都会不断增加,不会被释放。
闭包可以将函数的内部变量保存在内存中,这样它们就不能被释放。
6.3不必要的全局变量
声明太多的全局变量会导致变量内存常驻,直到进程结束才会释放内存。
6.4无效的DOM引用
//DOM仍然存在函数click(){//但是button变量的引用仍然在内存中。const button=document . getelementbyid(' button ');button . click();}//remove button元素函数remove BTN(){ document . body . remove child(document . getelementbyid(' button '));}
6.5定时器未清零
Vue的挂载点或component idmount component idmount(){ setinterval(function(){//.do某物},1000)}初始化Vue或react的页面生命周期时,定义了一个定时器,但离开页面后,定时器没有清零,会导致内存泄漏。
6.6事件监控为空
componentDidMount(){ window . addeventlistener(' scroll '),function () {//做点什么.});}和6.5中一样,页面生命周期初始化时,事件监听器是绑定的,但是离开页面后,事件监听器没有被清除,也会导致内存泄漏。
七、内存泄漏优化
1.解除…的关联
确保最小的内存消耗可以使页面获得更好的性能。优化内存使用的最佳方法是只保存执行代码所需的数据。一旦数据不再有用,最好通过将其值设置为null来释放其引用——。这种做法被称为取消引用
函数create person(name){ var localhomer=new Object();localPerson.name=name返回localPerson} var global person=createPerson(' Nicholas ');//手动取消引用globalPerson=null取消引用一个值并不意味着自动回收该值占用的内存。取消引用的真正效果是从执行环境中取出值,以便下次垃圾收集器运行时可以回收它。
2.提供手动清空变量的方法
var LeakArray=[];exports . clear=function(){ LeakArray=[];}3.业务不需要的内部功能可以在功能之外重构,闭包可以释放。4.避免创建太多生命周期长的对象,或将对象分解为多个子对象。
5.避免过度使用闭包
6.注意清除计时器和事件监听器
7.在nodejs中使用流或缓冲区操作大文件不会受到Nodejs内存的限制
8.使用外部工具(如redis)来缓存数据
摘要
JS是一种具有自动垃圾收集功能的编程语言。在浏览器中,垃圾主要通过移除标签来收集。在NodeJs中,垃圾主要通过生成恢复、清除、标签移除和增量标签等算法进行收集。在日常开发中,一些不被注意的编写方法可能会导致内存泄漏,因此我们需要更加关注自己的代码规范。
以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。