includeios/document

浏览器的垃圾回收机制

Opened this issue · 0 comments

浏览器的垃圾回收机制


1. 为什么要了解垃圾回收机制

  • 内存泄露:是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束
  • 堆栈溢出与内存泄露
let a = ()=>{
    b()
}

let b = () => {
    a()
}

a()

Error: Uncaught RangeError: Maximum call stack size exceeded


<--- Last few GCs --->

[43697:0x103800600]   342449 ms: Mark-sweep 1365.6 (1403.2) -> 1365.4 (1402.7) MB, 344.7 / 0.0 ms  (average mu = 0.781, current mu = 0.000) last resort GC in old space requested
[43697:0x103800600]   342774 ms: Mark-sweep 1365.4 (1402.7) -> 1365.4 (1402.7) MB, 325.1 / 0.0 ms  (average mu = 0.644, current mu = 0.000) last resort GC in old space requested

2.浏览器的回收机制原理

  • 垃圾收集器必须跟踪哪个变量有用哪个变量没用,对于不再有用的变量打上标记,以备将来回收其占用的内存,而浏览器实现标识无用变量的策略主要是下面两个方法:
方法一: 标记清除
  • 第一步:标记阶段(mark)从引用根节点开始标记所有被引用的对象
function markFromRoots(){
    const worklist = []
    for(let childNode in Roots){
        if(childNode != null && isNotMarked(childNode)){
            setMarked(childNode)
            worklist.push(childNode)
            mark()
        }
    }
}
    
        
function mark(){
    while(!isEmpty(worklist)){
        const PointNode = worklist.pop()
        for(let childNode in PointNode){
            if(childNode != null && isNotMarked(childNode)){
                setMarked(childNode)
                worklist.push(childNode)
            }
        }
    }
}
    
  • 第二步:清除(sweep)没有被标记的对象
functon sweep(start,end){
    while(start < end){
        const Node = Stack[start]
        if isMarked(Node)
            setUnMarked(Node)
        else free(Node)
        start ++
    }
}

sweep(Stack.startLocation,Stack.endLocation)
  • 在进行一次标记清除后,内存空间不再连续,会出现很多内存碎片,如果后续需要分配一个需要内存空间较大的对象时,如果所有的内存碎片不够用,将会使得浏览器无法完成此次分配,提前触发垃圾回收。
方法一点五: 标记整理
  • 标记清除的升级版
  • 清除完后将活着的对象移动到一起去。
  • IE8+,Firefox,Chrome,Opera等主流浏览器的JavaScript都是采取标记清除(整理)来进行垃圾回收的,只不过垃圾收集的时间和间隔互有不同
方法二: 引用计数
  • IE6,7对DOM对象进行此操作回收
  • 跟踪记录每个值被引用的次数。当一个对象(值)被赋给莫个变量时,则这个对象(值)的引用次数加1,如果这个变量的值变成另外一个(不再引用这个对象),那么这个对象的引用次数减1。当这个对象(值)的引用次数变为0时,说明这个变量没有在使用了,因此可以将其占用的空间收回。
let a = {}  //对象{}的引用次数+1 = 1
let b = a   //对象{}的引用次数+1 = 2
a = null    //对象{}的引用次数-1 = 1
b = null    //对象{}的引用次数-1 = 0 可以被回收了
  • 没有办法解决循环引用的问题
function test(){
    let a = {}
    let b = []
    a.prop = b
    b.prop = a
}
test()

//test执行完后,a和b的引用次数都是2,不会被回收
//标记清除不会有问题,此时test执行完后,两个对象都已经离开环境,会被标记清除
  • 可能会写出的循环引用:
window.onload = function(){
    var element = document.getElementById('example')
    element.onclick= function anotherFunction(){
        document.getElementById("anotherElement").innerHTML = "Processing...";
    }
    
    //解决办法
    element = null
}

chrome v8引擎(node)垃圾回收机制

  • 新生代:存放的对象生命周期很短,分配的内存比较小,垃圾回收比较频繁
  • 老年代:存放的对象生命周期比较长,分配的内存比较大,垃圾回收不是很频繁
新生代
  • Scavenge算法和cheney算法
  • 内存一分为二,一个处于使用的状态,称为Form空间,一个处于闲置的状态,称为To空间
  • 分配对象时,先是在Form空间进行分配的。当开始进行垃圾回收算法时,会检查Form空间的存活对象,将这些存活的对象复制到To空间中,而非活跃对象占用的空间将会被释放。
  • 完成复制后,Form空间和To空间的角色会发生对换
新生代 => 老生代
  • 对象从From空间复制到To空间时,会检查它的内存地址来判断这个对象是否已经经历过一个新生代的清理,如果是,则复制到老生代中,否则复制到To空间中
  • 对象从From空间复制到To空间时,如果To空间已经被使用了超过25%,那么这个对象直接被复制到老生代
老生代
  • 标记清除和标记整理结合,主要采用标记清除算法,如果空间不足以分配从新生代晋升过来的对象时,才使用标记整理

造成内存泄露的情况

  • 全局变量
  • DOM元素的引用(IE):手动删除
  • 被遗忘的定时器(IE):手动清除
  • 闭包
let something = null
let someMessage = 'Hello'
let replacething = ()=>{
    let anotherthing = something
    
    let unused = ()=>{
      if(anotherthing){
        console.log('hi')
      }
    }
    
    something = {
      longStr:new Array(10000000).join('*'),
      someMethod:()=>{
        console.log(someMessage)
      }
    }
}

//每次replacething被调用,something都会获取一个新的对象,包含一个大数组和一个新的闭包(someMethod)
//unused也保持一个闭包(anotherthing)
//someMethod与unused共享作用域,即使它未使用,但是它对originalThing的引用强制它保持活动

Chrome查看工具

https://www.jianshu.com/p/504bde348956
https://blog.csdn.net/c11073138/article/details/84700482