宁波网站建设公司推荐易企网,如何微信支付购物网站,佛山新网站建设,网站建设与维护 排序题对于值类型b来说#xff0c;就直接释放了其占用的内存#xff0c;对于引用类型obj来说#xff0c;销毁的只是变量obj对堆内存地址 1001 的引用#xff0c;obj的值 { c: 3 } 依然存在于堆内存中。那么堆内存中的变量如何进行回收呢#xff1f;
V8的垃圾回收策略主要是基于…对于值类型b来说就直接释放了其占用的内存对于引用类型obj来说销毁的只是变量obj对堆内存地址 1001 的引用obj的值 { c: 3 } 依然存在于堆内存中。那么堆内存中的变量如何进行回收呢
V8的垃圾回收策略主要是基于分代式垃圾回收机制其根据对象的存活时间将内存的垃圾回收进行不同的分代然后对不同的分代采用不同的垃圾回收算法。在新生代的垃圾回收过程中主要采用了Scavenge算法在老生代采用标记清除和标记整理算法。 全停顿
垃圾回收算法在执行前需要将应用逻辑暂停执行完垃圾回收后再执行应用逻辑这种行为称为 「全停顿」。例如如果一次GC需要50ms应用逻辑就会暂停50ms。
全停顿的目的是为了解决应用逻辑与垃圾回收器看到的情况不一致的问题。
JavaScript中会被判定为垃圾的情形如下
对象不再被引用对象不能从根上访问到 GC算法
常见的GC算法如下
引用计数标记清除标记整理分代回收
浏览器的垃圾回收机制
浏览器垃圾回收机制根据数据的存储方式分为栈垃圾回收和堆垃圾回收。
栈垃圾回收当一个函数执行结束之后JavaScript 引擎会通过向下移动 ESP 来销毁该函数保存在栈中的执行上下文遵循先进后出的原则。
堆垃圾回收当函数直接结束栈空间处理完成了但是堆空间的数据虽然没有被引用但是还是存储在堆空间中需要垃圾回收器将堆空间中的垃圾数据回收。
在V8中将内存一分为二分为了新生代和老生代。它们特点如下
新生代对象的存活时间较短。新生的对象或只经过一次垃圾回收的对象。老生代对象存活时间较长。经历过一次或多次垃圾回收的对象。
我们可以通过 --max-old-space-size命令设置老生代空间的最大值--max-new-space-size 命令设置新生代空间的最大值。老生代与新生代的空间大小在程序初始化时设置一旦生效则不能动态改变。
新生区中使用Scavenge清除算法老生区中使用标记-清除算法和标记-整理算法。
新生代垃圾回收副垃圾回收器
新生代的特点
通常把小的对象分配到新生代新生代的垃圾回收比较频繁通常存储容量在1~8M
新生代中垃圾回收算法
Scavenge算法标记-复制-角色反转
回收新生代对象主要采用复制算法Scavenge 算法加标记整理算法。而Scavenge 算法的具体实现主要采用了Cheney算法。 Cheney算法将内存分为两个等大空间使用空间为From空闲空间为To。
过程如下
从 From 空间分配对象若 semispace 被分配满则执行 Scavenge 算法进行垃圾回收。对对象区域中的垃圾进行标记检查 From 空间的存活对象若对象存活则检查对象是否符合晋升条件若符合条件则晋升到老生代否则将对象从 From 空间复制到 To 空间。并且有序的排列起来复制后空闲区域就没有内存碎片了若对象不存活则释放不存活对象的空间。完成复制后将 From空间对象区域与 To 空间空闲区域进行角色翻转flip。这样就完成了垃圾对象的回收操作同时这种角色翻转的操作还能让新生代中的这两块区域无限重复使用下去 一轮GC还存活的新生代需要晋升。 当对象从From 空间复制到 To 空间时若 To 空间使用超过 25%则对象直接晋升到老生代中。
缺点由于只能使用堆内存的一半所以不适用大规模的垃圾回收机制中 在讲解老生代Mark-Sweep(标记清除)和Mark-Compact(标记整理)算法之前先来回顾一下引用计数法对于对象A任何一个对象引用了A的值计数器1引用失效时计数器-1当计数器为0时责备回收但是会存在循环引用的情况可能会导致内存泄漏自2012年起所有的现代浏览器均放弃了这种算法。 引用计数
早期的浏览器最常使用的垃圾回收方法叫做引用计数语言引擎有一张引用表保存了内存里面所有的资源通常是各种值的引用次数。如果一个值的引用次数是0就表示这个值不再用到了因此可以将这块内存释放。 引用计数有一个问题就是循环引用 引用计数算法优点
引用计数为零时发现垃圾立即回收最大限度减少程序暂停
引用计数算法缺点
无法回收循环引用的对象空间开销比较大
老生代垃圾回收主垃圾收集器
因为新生代存储容量小很容易写满所以经过两次垃圾回收之后依然活动的对象就会被移动到老生代中这个策略被称为对象晋升策略。
晋升条件 对象晋升的条件主要有两个。 1、对象在新生代期间是否经历过Scavenge回收 2、是To空间的内存占用比超过限制To空间内存消耗是否超过25%如果超过对象直接晋升设置为25%的比例的原因是当完成 Scavenge 回收后To 空间将翻转成From 空间继续进行对象内存的分配。若占比过大将影响后续内存分配因此超过这个限制之后对象会被直接转移到老生代来进行管理 ————————————————
回收老生代对象主要采用标记清除、标记整理、增量标记算法主要使用标记清除算法只有在内存分配不足时采用标记整理算法。
首先使用标记清除完成垃圾空间的回收采用标记整理进行空间优化采用增量标记进行效率优化 标记清除
核心思想分标记和清除两个阶段完成。
标记标记阶段就是从一组根元素开始递归遍历这组根元素在这个遍历过程中能到达的元素称为活动对象没有到达的元素就可以判断为垃圾数据。标记存活的对象清除将垃圾数据进行清除。
对比引用计数算法标记清除算法最大的优点是能够回收循环引用的对象它也是v8引擎使用最多的算法。 缺点对一块内存多次执行标记 - 清除算法后会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存。此时需要对内存碎片进行整理。
这种内存碎片会对后续的内存分配造成问题因为很可能出现需要分配一个大对象的情况这时所有的碎片空间都无法完成此次分配就会提前触发垃圾回收而这次回收是不必要的。 标记整理
与标记清除呈现一个策略递进关系当空间不足以对从新生代中晋升过来的对象进行分配时才使用在整理的过程中将活着的对象往一端移动移动完成后直接清理掉边界外的内存。这也是两者最大的区别。标记整理对待未存活对象不是⽴即回收⽽是将存活对象移动到⼀边然后直接清掉端边界以外的内存。
1. 标记和标记 - 清除的标记过程一样从一组根元素开始递归遍历这组根元素在这个遍历过程中能到达的元素标记为活动对象。
2. 整理让所有存活的对象都向内存的一端移动
3. 清除清理掉边界以外的内存 V8 是使用副垃圾回收器和主垃圾回收器处理垃圾回收的不过由于 JavaScript 是运行在主线程之上的一旦执行垃圾回收算法都需要将正在执行的 JS 脚本暂停下来待垃圾回收完毕后再恢复脚本执行。这种行为叫做全停顿。 为了降低老生代的垃圾回收而造成的卡顿V8 将标记过程分为一个个的子标记过程同时让垃圾回收标记和 JS 应用逻辑交替进行直到标记阶段完成这个算法称为增量标记 这里为了便于理解引用两个流程图。 增量标记 新生代和老生代回收对比
新生代由于占用空间比较少采用空间换时间机制。 老生代区域空间比较大不太适合大量的复制算法和标记整理所以最常用的是标记清除算法为了就是让全停顿的时间尽量减少。 全停顿 V8 是使用副和主垃圾回收器处理垃圾回收的不过由于 js是运行在主线程之上的一旦执行垃圾回收算法都需要将正在执行的js 脚本暂停下来待垃圾回收完毕后再恢复脚本执行。我们把这种行为叫做全停顿Stop-The-World。 将原本需要一次性遍历堆内存的操作改为增量标记的方式先标记堆内存中的一部分对象然后暂停将执行权重新交给JS主线程待主线程任务执行完毕后再从原来暂停标记的地方继续标记直到标记完整个堆内存。 即把垃圾回收这个⼤的任务分成⼀个个⼩任务穿插在 JavaScript任务中间执⾏ 这个理念其实有点像React框架中的Fiber架构只有在浏览器的空闲时间才会去遍历Fiber Tree执行对应的任务否则延迟执行尽可能少地影响主线程的任务避免应用卡顿提升应用性能。
得益于增量标记的好处V8引擎后续继续引入了延迟清理(lazy sweeping)和增量式整理(incremental compaction)让清理和整理的过程也变成增量式的。同时为了充分利用多核CPU的性能也将引入并行标记和并行清理进一步地减少垃圾回收对主线程的影响为应用提升更多的性能。 识别内存泄漏的方法 performance
点击 检查的performance 然后点击录制 执行我们觉得比较消耗内存的操作 然后stop录制。 可以看到内存在短时间消耗的比较快下降的小凹槽就是浏览器在进行垃圾回收
垃圾回收优化策略 1.延迟回收
因为垃圾hi收会有一个运行的阻塞所以可以选择在cpu空闲时候时候进行垃圾回收从而尽可能减少对应用程序运行的影响
2.增量标记
由于全堆垃圾回收会导致JS应用暂停执行为了减少全堆垃圾回收带来的卡顿V8采用增量标记的策略。也就是将一次完整的垃圾回收分解为多个小的步骤同时让垃圾回收和应用逻辑交替执行以达到流畅的用户体验。
3.对象晋升
在新生代中存活下来的对象会被移动到老生代中这就是对象晋升策略。在V8中通常采用两次垃圾回收后仍然存活的对象会被晋升到老生代。
当进行大规模的垃圾回收时V8引擎使用增量标记来减少对应用程序的阻塞。
增量标记是一种垃圾回收的优化策略它将一次完整的垃圾回收过程分解为多个小的步骤使得垃圾回收和应用程序的逻辑可以交替执行。这样可以减少垃圾回收造成的长时间阻塞提高应用程序的响应性和用户体验。
V8引擎的增量标记策略主要包括以下步骤
初始标记Initial Marking在这个阶段V8会标记出根对象和直接从根对象可达的对象确定它们为活动对象。这个阶段需要阻塞应用程序的执行但是尽量保持时间短暂。
并发标记Concurrent Marking在初始标记之后V8引擎会启动增量标记线程与应用程序的执行并发进行。增量标记线程会遍历剩余的对象图标记出所有的活动对象。同时应用程序的逻辑也在继续执行。
再标记Remark在并发标记过程中应用程序可能会继续修改对象的引用关系因此需要进行再标记。再标记阶段会对并发标记过程中发生变化的对象进行重新标记以确保准确性。
清除阶段Sweeping在增量标记完成后V8引擎会进行清除阶段回收非活动对象所占用的内存。这个阶段通常会阻塞应用程序的执行因为它需要遍历堆中的所有对象。
通过增量标记的方式V8引擎可以在垃圾回收过程中与应用程序的逻辑交替执行减少长时间的阻塞。这种方式可以有效降低垃圾回收对应用程序性能的影响提高应用程序的响应速度和用户体验。 ————————————————
性能优化即如何避免内存泄漏
1.尽可能减少全局变量的使用
1.避免使用全局变量
全局变量会挂载在window下全局变量至少有一个引用计数全局变量存活更久持续占用内存在明确数据作用域的情况下尽量使用局部变量如果确实需要使用全局变量确保在使用完毕后将其设置为 null以便垃圾回收机制可以及时释放内存。
2.手动清除定时器以及不用闭包 在使用定时器时一定要记得在适当的时机手动清除定时器。如果忘记清除定时器定时器的回调函数将持续执行可能导致内存泄漏。确保在不需要定时器时使用 clearTimeout 或 clearInterval 主动清除定时器。 3.清除 DOM 引用
当操作 DOM 元素时确保在不再需要使用它们时清除对 DOM 元素的引用。如果仍然保留对已移除或隐藏的 DOM 元素的引用这些元素将无法被垃圾回收。 4.使用弱引用weakMap 5.减少判断层级
function doSomething(part, chapter) {const parts [ES2016, 工程化, Vue, React, Node]if (part) {if (parts.includes(part)) {console.log(属于当前课程)if (chapter 5) {console.log(您需要提供 VIP 身份)}}} else {console.log(请确认模块信息)}
}doSomething(Vue, 6)// 减少判断层级
function doSomething(part, chapter) {const parts [ES2016, 工程化, Vue, React, Node]if (!part) {console.log(请确认模块信息)return}if (!parts.includes(part)) returnconsole.log(属于当前课程)if (chapter 5) {console.log(您需要提供 VIP 身份)}
}doSomething(Vue, 6)
6.减少数据读取次数 对于频繁使用的数据我们要对数据进行缓存。
div idskip classskip/divscriptvar oBox document.getElementById(skip)// function hasEle (ele, cls) {// return ele.className cls// }function hasEle (ele, cls) {const className ele.classNamereturn className cls}console.log(hasEle(oBox, skip))
/script 7.事件绑定优化
ul classulliHello World!/lili25/lili岂曰无衣与子同袍/li
/ulscriptvar list document.querySelectorAll(li)function showTxt(ev) {console.log(ev.target.innerHTML)}for (item of list) {item.onclick showTxt}// 优化后function showTxt(ev) {var target ev.targetif (target.nodeName.toLowerCase() li) {console.log(ev.target.innerHTML)}}var ul document.querySelector(.ul)ul.addEventListener(click, showTxt)
/script 8.避开闭包陷阱
button classbtn点击/buttonscriptfunction foo() {let el document.querySelector(.btn)el.onclick function() {console.log(el.className)}}foo()// 优化后function foo1() {let el document.querySelector(.btn)el.onclick function() {console.log(el.className)}el null // 将el置为 null 防止闭包中的引用使得不能被回收}foo1()
/script
与weakMap的关联
通过【垃圾回收机制】的角度认识【Map与WeakMap】的区别 - 知乎 (zhihu.com)
V8 垃圾回收机制与 WeakMap 之间有一些联系主要涉及到垃圾回收对于弱引用的处理。以下是它们之间的关系
1.弱引用和垃圾回收 WeakMap 中的键是弱引用的。这意味着如果没有其他引用指向 WeakMap 中的键对象这些键对象可以被垃圾回收。垃圾回收器在执行时会检测并处理弱引用当检测到某个对象的引用计数为零时可以安全地回收该对象。 2.避免内存泄漏 由于 WeakMap 的键是弱引用当键对象不再被其他部分引用时它们可以被垃圾回收相应的键值对也会从 WeakMap 中自动删除。这有助于防止一些潜在的内存泄漏问题因为对象在 WeakMap 中的存在不会阻止它们被垃圾回收。 3.私有数据存储 WeakMap 通常被用于存储对象的私有数据因为这样的数据不会影响对象的垃圾回收。这使得在不破坏封装性的情况下关联额外信息成为可能。
let weakMap new WeakMap();let obj {};
weakMap.set(obj, some private data);// 当 obj 不再被引用时垃圾回收可以回收 obj并清理 weakMap 中对应的项。
obj null;
总体而言WeakMap 的设计与垃圾回收机制的协同工作有助于更有效地管理对象的生命周期避免潜在的内存泄漏问题同时提供一种安全地存储私有数据的机制。 Map和WeakMap都是JavaScript的内置数据结构用于存储键值对。它们之间的主要区别在于以下几点
键类型的限制在Map中键可以是任意类型的值包括基本类型和对象引用而在WeakMap中键只能是对象引用。这是因为WeakMap的键是弱引用不会阻止对象被垃圾回收使得WeakMap更适合于存储对象之间的关联信息。垃圾回收机制在Map中如果某个键不再被引用它仍然会被Map引用并且不会被垃圾回收。而在WeakMap中如果某个键不再被引用它会被自动从WeakMap中删除这也是WeakMap的一个特性可以避免内存泄漏。迭代在Map中可以使用Map.prototype.keys()、Map.prototype.values()和Map.prototype.entries()等方法来迭代Map中的键、值或键值对而在WeakMap中由于键是对象引用无法直接迭代键或值。功能Map相比WeakMap提供了更多的功能比如可以获取Map的大小使用Map.prototype.size属性可以通过键获取值使用Map.prototype.get()方法可以遍历Map中的键值对等。而WeakMap相对简单只提供了WeakMap.prototype.get()、WeakMap.prototype.set()、WeakMap.prototype.has()和WeakMap.prototype.delete()等基本操作。