lxinr/interview-question

2021/01/30 - JavaScript内存机制

lxinr opened this issue · 0 comments

lxinr commented

javascript的内存空间分为栈(stack)、堆(heap)、池(一般也会归类为栈中), 其中栈存放变量,堆存放复杂对象,池存放常量,所以也叫常量池

栈结构遵循后进先出(LIFO),其中基本类型是保存在栈中,因为这些类型在内存中分别占有固定大小的空间,通过按值来访问

var arr = [1, 2, 3]
arr.push(4)
console.log(arr.pop()) // 4 后进先出

堆数据结构是一种树状结构。它的存取数据的方式与书架和书非常相似。我们只需要知道书的名字就可以直接取出书了,并不需要把上面的书取出来。JSON格式的数据中,我们存储的key-value可以是无序的,因为顺序的不同并不影响我们的使用,我们只需要关心书的名字,其中引用类型保存在堆内存中,因为这种值的大小不固定,因此不能把它们保存到栈内存中,但对象引用是放在栈内存中的当查询引用类型的变量时, 先从栈中读取内存地址, 然后再通过地址找到堆中的值

内存回收

JavaScript有自动垃圾收集机制,垃圾收集器会每隔一段时间就执行一次释放操作,找出那些不再继续使用的值,然后释放其占用的内存

  • 局部变量和全局变量的销毁
    • 局部作用域中,当函数执行完毕,局部变量也就没有存在的必要了,因此垃圾收集器很容易做出判断并回收
    • 全局变量什么时候需要自动释放内存空间则很难判断,所以在开发中尽量避免使用全局变量
  • 以Google的V8引擎为例,V8引擎中所有的JS对象都是通过来进行内存分配的
    • 初始分配:当声明变量并赋值时,V8引擎就会在堆内存中分配给这个变量
    • 继续申请:当已申请的内存不足以存储这个变量时,V8引擎就会继续申请内存,直到堆的大小达到了V8引擎的内存上限为止
  • V8引擎对堆内存中的JS对象进行分代管理
    • 新生代:存活周期较短的JS对象,如临时变量、字符串等
    • 老生代:经过多次垃圾回收仍然存活,存活周期较长的对象,如主控制器、服务器对象等

垃圾回收算法

1、引用计数(现代浏览器不再使用)

引用计数算法定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。如果没有其他对象指向它了,说明该对象已经不再需要了,
但假如有两个对象相互引用,尽管他们已不再使用,但是垃圾回收器不会进行回收,最终可能会导致内存泄露

2、标记清除

标记清除算法将“不再使用的对象”定义为无法到达的对象。即从根部(在JS中就是全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象,保留。那些从根部出发无法触及到的对象被标记为不再使用,稍后进行回收

内存泄漏

对于不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)

识别方法
1、浏览器方法
  1. 打开开发者工具,选择 Memory
  2. 在右侧的Select profiling type字段里面勾选 timeline
  3. 点击左上角的录制按钮。
  4. 在页面上进行各种操作,模拟用户的使用情况。
  5. 一段时间后,点击左上角的 stop 按钮,面板上就会显示这段时间的内存占用情况