← 返回目录

内存管理GC

第 84 / 154 卷日期:

代码。

async函数时Generator函数的语法糖,async函数的返回值是promise对象,generator函数的返回值是 iterator对象方便多了,同时,还可以使用 await 代替then 方法指定下一步操作。

function f() {

return Promise.resolve('TEST');

}

// asyncF is equivalent to f!

async function asyncF() {

return 'TEST';

}

await 会阻塞下面的代码(即加入微任务队列),先执行 async外面的同步代码,同步代码执行完,再回到 async 函数中,再执行之前阻塞的代码

定时器原理

var id=setTimeout(fn,delay); 初始化一个只执行一次的定时器,这个定时器会在指定的时间延迟 delay 之后调用函数 fn ,该 setTimeout 函数返回定时器的唯一 id,我们可以通过这个 id 来取消定时器的执行。

var id=setInvertal(fn,delay); 与 setTimeout 类似,只是它会以 delay 为周期,反复调用函数 fn,直到我们通过id取消该定时器。

clearInterval(id),clearTimeout(id); 这两个函数接受定时器的 id,并停止对定时器中指定函数的调用。

定时器指定的延迟时间不能得到保证

在浏览器中,因为所有的JS代码都运行在单一线程中,异步事件只有在被触发时,其回调才会得到执行。

因为单线程的缘故,在同一时间只能执行一条 JavaScript 代码,每一个代码块都会阻塞其他异步事件的执行。

这就意味着,当一个异步事件发生的时候(例如鼠标点击,定时器触发,一个 XMLHttpRequest 请求完成),它进入了代码的执行队列,执行线程空闲时会依照该执行队列中顺序依次执行代码。

总结:

JavaScript 引擎是单线程的,会迫使异步事件进入执行队列,等待执行。

setTimeout 和 setInterval 在执行异步代码时从根本上是有所不同的。

如果一个定时器事件被阻塞,使得它不能立即执行,那么它会被延迟,直到下一个可能的时间点,才被执行(这可能比你指定的 delay 时间要长)

Interval 的回调有可能‘背靠背’无间隔的执行,这种情况是说 interval 的回调函数的执行时间比我们指定的 delay 时间还要长

内存管理GC

栈中JS引擎自动清除.

JS 引擎可以通过逃逸分析辨别出哪些变量需要存储在堆上,哪些需要存储在栈上。

自动垃圾回收机制:找出不使用的值,释放内存。

函数运行结束,没有闭包或引用,局部变量被标记清除

全局变量:浏览器卸载页面被清除

垃圾回收算法

不论哪个垃圾回收算法,都有一套共同的流程:

  1. 标记内存空间中的活动对象(在使用中的对象)和非活动对象(可以回收的对象)。
  2. 删除非活动对象,释放内存空间。
  3. 整理内存空间,避免频繁回收后产生的大量内存碎片(不连续内存空间)。

引用计数

一个对象是否有被引用(循环引用导致内存泄露)

策略:跟踪每个变量被使用的次数

  1. 当声明了一个变量且将一个引用类型赋值给该变量时,这个值的引用次数为1
  2. 若同一个值又被赋给另一个值,引用数 +1
  3. 如果该变量的值被其他的值覆盖了,则引用次数减 1
  4. 当这个值的引用次数变为 0 的时候,说明没有变量在使用,这个值没法被访问了,回收空间,垃圾回收器会

在运行的时候清理掉引用次数为 0 的值占用的内存

缺点:

需要一个计数器,所占内存空间大,因为我们也不知道被引用数量的上限。

解决不了循环引用导致的无法回收问题。

标记清除

将“不再使用的对象”定义为“无法到达的对象”

工作流程:

  1. 垃圾收集器在运行时给内存变量加上标记,假设内存汇总所有对象都是垃圾,全标记为0
  2. 从根部出发,寻找可到达的变量,并将其标记清除,改为1
  3. 留有标记的变量就是待删除的,即标记为0,销毁并回收它们占用的内存
  4. 把所有内存中对象标记修改为0,等待下一轮垃圾回收

优点:实现简单,一位二进制位就可以为其标记

缺点:

内存碎片化,空闲内存块不连续分配速度慢,因为即使使用first-fit策略,其操作仍是一个O(n)的操作,最坏情况是每次都要遍历到最后,因为碎片

化,大对象的分配速率会更慢

  1. 先在 from 空间进行内存分配,当空间被占满时,标记活动对象,并将其复制到 to 空间。
  2. 复制完成后,将 from 和 to 空间互换。

由于直接将活动对象复制到另一半空间,没有了清除阶段的开销,所以能在较短时间内完成回收操作,并且每次复制的时候,对象都会集中到一起,相当于同时做了整理操作,避免了内存碎片的产生。

优点:吞吐量高、没有碎片

缺点:首先,复制操作需要时间成本的,若堆空间很大且活动对象很多,则每次清理时间会很久;其次,将空间二等分的操作,让可用的内存空间直接减少了一半。

标记整理

也叫做标记-压缩算法。结合了标记-清除和复制算法的优点。

  1. 从一个 GC root 集合出发,标记所有活动对象。
  2. 将所有活动对象移到内存的一端,集中到一起。
  3. 直接清理掉边界以外的内存,释放连续空间。

该算法既避免了标记-清除法产生内存碎片的问题,又避免了复制算法导致可用内存空间减少的问题。当然,该算法也不是没有缺点的,由于其清除和整理的操作很麻烦,甚至需要对整个堆做多次搜索,故而堆越大,耗时越多。

识别内存泄露

经验法则是,如果连续五次垃圾回收之后,内存占用一次比一次大,就有内存泄漏。

这就要求实时查看内存的占用情况。

在 Chrome 浏览器中,我们可以这样查看内存占用情况

1.打开开发者工具,选择 Performance 面板

2.在顶部勾选 Memory

3.点击左上角的 record 按钮

4.在页面上进行各种操作,模拟用户的使用情况

5.一段时间后,点击对话框的 stop 按钮,面板上就会显示这段时间的内存占用情况

造成内存泄露

  1. 意外的全局变量
  2. 被遗忘的定时器和回调函数
  3. 事件监听没有移除
  4. 没有清理的DOM 引用
  5. 子元素存在的内存泄漏
  6. 闭包

V8对GC优化

栈中数据回收:执行状态指针 ESP 在执行栈中移动,移过某执行上下文,就会被销毁;

对象复制排列到空闲区域,没有内存碎片,完成后,清理对象区域,角色反转;

新生代区域两次垃圾回收还存活的对象晋升至老生代区域;

主垃圾回收器负责老生区垃圾回收,大对象,存活时间长;

新生代区域采用标记-清除算法回收垃圾:从根元素开始,递归,可到达的元素活动元素,否则是垃圾数据;

为了不造成卡顿,标记过程被切分为一个个子标记,交替进行。

分代式垃圾回收

以上所说的垃圾清理算法每次垃圾回收时都要检查内存中所有的对象,酱紫对一些大,老,存活时间长的对象来说,同新,小,存活时间短的对象一个频率的检查很不好,因为前者需要时间长且不需要频繁进行清理,后者恰恰相反,如何

优化?

分代式

分代式机制把一些新、小、存活时间短的对象作为新生代,采用一小块内存频率较高的快速清理,而一些大、老、存活时间长的对象作为老生代,使其很少接受检查,新老生代的回收机制及频率是不同的,可以说此机制的出现很

大程度提高了垃圾回收机制的效率

新老生代

V8的GC策略基于分代式垃圾回收机制,将堆内存分为新生代和老生代两区域,采用不同的垃圾回收策略进行垃圾回收.

新生代的对象是存活时间较短的对象,简单来说就是新产生的对象,通常只支持1-8M的容量,而老生代的对象为存活时间较长或常驻内存的对象.

新生代垃圾回收

新生代中对象一般存活时间较短,采用 scavenge 算法处理,在Scavenge算法具体实现中,主要采用一种复制式的方法及Cheney算法:

其将新生代空间对半分为 from-space 和 to-space 两个区域。新创建的对象都被存放到 from-space,当空间快被写满时触发垃圾回收。先对 from-space 中的对象进行标记,完成后将标记对象复制到 to-space 的一端,然后将两

个区域角色反转,就完成了回收操作。

由于每次执行清理操作都需要复制对象,而复制对象需要时间成本,所以新生代空间会设置得比较小(1~8M)。

当一个对象经过多次复制后依然存活,它将会被认为是生命周期较长的对象,随后会被移动到老生代中,采用老生代的垃圾回收策略进行管理

另外还有一种情况,如果复制一个对象到空闲区时,空闲区空间占用超过了 25%,那么这个对象会被直接晋升到老生代空间中,设置为 25% 的比例的原因是,当完成 Scavenge 回收后,空闲区将翻转成使用区,继续进行对象内

存的分配,若占比过大,将会影响后续内存分配

老生代垃圾回收

老生代中的对象一般存活时间较长且数量也多,使用了两个算法,分别是标记清除算法和标记压缩算法。

什么情况下对象会出现在老生代空间中:

新生代中的对象是否已经经历过一次 Scavenge 算法,如果经历过的话,会将对象从新生代空间移到老生代空间中。

To 空间的对象占比大小超过 25 %。在这种情况下,为了不影响到内存分配,会将对象从新生代空间移到老生

全站分卷索引 · 154 卷
  1. 1代码随想录知识星球精华(最强八股文)第五版(前端篇))
  2. 2html语义化
  3. 3src 和 href 的区别
  4. 4link 与 @import 的区别
  5. 5iframe
  6. 6CSS
  7. 7伪元素和伪类
  8. 8block,inline,inline-block
  9. 9重排、重绘和合成
  10. 10盒子模型
  11. 11margin 合并
  12. 12margin和padding 的值为百分比时
  13. 13浮动
  14. 14定位
  15. 15CSS选择器
  16. 16CSS选择器优先级
  17. 17选择器权重Specificity
  18. 18CSS属性继承
  19. 19不可/可继承属性
  20. 20display: none与 visibility: hidden的区别
  21. 21background-size
  22. 22client、offset&scroll
  23. 23CSS3新特性
  24. 24flex布局
  25. 25常见布局
  26. 26三栏布局
  27. 27垂直居中
  28. 28水平垂直居中
  29. 29CSS画三角形
  30. 30使chrome支持12px以下文字
  31. 31响应式布局
  32. 32媒体查询
  33. 33过渡与动画
  34. 34JavaScript 动画 和 CSS动画
  35. 35SCSS
  36. 36CSS性能优化
  37. 37有哪些数据类型
  38. 38判断数据类型
  39. 39类型转换
  40. 40parseInt&parseFloat
  41. 41浮点数求和步骤
  42. 42十进制小数转二进制
  43. 430.1+0.2 为什么不等于 0.3
  44. 44undefined 和 undeclared 的区别
  45. 45JavaScript NaN 属性
  46. 46isFinite()&isNaN()
  47. 47数组
  48. 48对原数组有影响
  49. 49对原数组无影响
  50. 50数组去重
  51. 51数组拍平
  52. 52ES6新增的数组方法
  53. 53判断是否存在某个值
  54. 54arguments为啥不是数组
  55. 55对象
  56. 56遍历对象的方法
  57. 57判断对象是否具有属性
  58. 58for in 和 for of 的区别
  59. 59继承方式
  60. 60instanceof
  61. 61深浅拷贝
  62. 62freeze属性
  63. 63函数
  64. 64函数柯里化
  65. 65函数的 length
  66. 66箭头函数和普通函数区别
  67. 67构造函数
  68. 68执行上下文
  69. 69作用域
  70. 70作用域和作用域链
  71. 71闭包和内存泄漏
  72. 72this
  73. 73关于 this 的指向问题
  74. 74改变this指向的方法(bind / call / apply)
  75. 75字符串
  76. 76DOM和事件
  77. 77哪些事件支持冒泡
  78. 78阻止冒泡
  79. 79HTMLCollection、NodeList
  80. 80dom.onclick 和 dom.addEventListener的区别
  81. 81e.target和e.currentTarget
  82. 82页面生命周期
  83. 83异步
  84. 84内存管理GC
  85. 85其它
  86. 86JavaScript 中日期时间格式转换
  87. 87input和object实现双向绑定
  88. 88JSON.stringify()
  89. 89escape、encodeURI&encodeURIComponent
  90. 90移动端滚动穿透和溢出
  91. 91ES6
  92. 92Map字典
  93. 93... 与rest
  94. 94proxy
  95. 95模块化
  96. 96Ajax、Fetch和Axios
  97. 97Webpack
  98. 98浏览器:事件循环
  99. 99浏览器内核
  100. 100浏览器同源策略
  101. 101输入URL回车后发生了什么
  102. 102HTTP
  103. 103HTTP 2.0
  104. 104浏览器存储
  105. 105鉴权
  106. 106Webstorage
  107. 107浏览器执行机制
  108. 108AST 抽象语法树
  109. 109性能优化
  110. 110CSS
  111. 111网络相关
  112. 112使用HTTP2.0
  113. 113图片
  114. 114CDN图片
  115. 115其他文件优化
  116. 116降低APP首页开屏渲染时间
  117. 117页面白屏
  118. 118HTTPS中间人攻击
  119. 119Web Worker
  120. 120Service Worker
  121. 121微前端
  122. 122Vue
  123. 123v-show 和 v-if 的区别
  124. 124双向数据绑定的原理
  125. 125Vue中的插槽使用
  126. 126Vue中的key
  127. 127Vue过滤器
  128. 128Vue中Scoped原理
  129. 129Vue中$nextTick的使用
  130. 130Vue中实现组件通信的方式
  131. 131Vue-router
  132. 132Vue路由传参
  133. 133前端路由
  134. 134Vuex
  135. 135虚拟DOM
  136. 136DIFF 算法
  137. 137Vue3和Vue2有哪些区别
  138. 138说一说你对React的理解
  139. 139React事件机制
  140. 140说一说你对react refs的理解
  141. 141react中key的作用
  142. 142组件开发
  143. 143有状态组件和无状态组件
  144. 144函数式组件和类组件的区别
  145. 145PureComponent&Component
  146. 146常见 Hooks
  147. 147状态管理
  148. 148redux toolkit
  149. 149React18新特性
  150. 150client中的createRoot取代以前的ReactDOM.render
  151. 151Suspense
  152. 152新的Hooks
  153. 153VDom一定更快吗
  154. 154Fiber