JavaScript之垃圾回收机制

我心飞翔 分类:javascript

JavaScript是如何实现自动回收垃圾机制的?

它是通过确定哪个变量不会再使用,然后释放它占用的内存。垃圾回收程序不是无时无刻都在运行着,它会每间隔一段时间就会自动运行程序,清除哪些不再使用的内存。

那是如何检测哪个变量不再使用了呢?

通过标记清理和引用计数两种方法,可以进行检测哪个变量不再使用,但引用计数方法,在某些场景下却有一些弊端,比如循环引用,下面先讲一下引用计数,之后再讲一下标记清除。

全局上下文中的变量和函数,只有当页面标签关闭时,才会释放全局上下文中的所占用的内存,而函数上下文和块级上下文只要代码块中的代码执行完毕,就会释放它们所占用的内存。

基于引用策略的垃圾回收程序

function  b(){
    let obj={
        name:"小黑",
        age:18,
        sex:"男",
    } //对象被引用1次
    return  obj;
}

let obj=b();
console.log(obj);
obj=null;//对象被引用0次,下次执行垃圾回收程序时,会回收引用次数为0的对象
 

上面的对象最后被引用的次数是1次,为什么不是2次呢?

因为当函数调用完毕时,函数上下文出栈,函数里的所有变量都会自动被解除引用,所以现在该对象被引用的次数是1,在全局上下文中不会自动地解除对象的引用,如果需要解除引用,需要手动地将变量赋值为null。

引用计数的垃圾回收策略是有局限性的,来看一下下面的代码

function a(){
    let obj1=new  Object();
    let obj2=new  Object();
    obj1.a=obj2;
    obj2.a=obj1;
}

a();
 

引用清除1.jpg

引用清除2.jpg

当函数a被调用时,函数a的执行上下文被压入执行栈中,然后对函数体里的变量进行变量提升,之后执行代码,变量obj1创建了一个对象(这里给这个对象一个别名叫b,为了后面方便描述),变量obj2也创建了一个对象(别名a),然后obj1的属性a引用了对象obj2,obj2的属性a引用了对象obj1,此时对象a和对象b都被引用了2次。当函数a调用完毕时,函数a释放了它所占用的内存,但是对象a和对象b它们之间互相引用,所以对象a和对象b不会被垃圾回收程序回收内存!这就导致了内存泄漏。

正是因为引用计数会导致循环引用的问题,现在的垃圾回收程序大多采用标记清理的策略进行垃圾回收!

基于标记清理的垃圾回收程序

标记清理策略它会将所有在全局上下文的变量,以及被在全局上下文中的变量引用的变量添加上标记。在此之后没有被添加上标记的变量就是待删除的了,原因是任何在全局上下文的变量都访问不到它们了。随后垃圾回收程序做一次内存清理。

function a(){
    let name="小黑";
    return name;
}
function b(){
    let obj={
        name:"小红",
        age:18,
        sex:"男"
    }
    return  obj;
}

let c=a();

let d=b();

console.log(name);
console.log(obj);

 

标记清除.jpg

标记清除2.png

想一想哪个函数会造成内存泄漏?

函数a中的变量name是原始类型数据(String),当调用函数a时,把字符串的值"小黑"赋值给了变量c,调用完函数a后,函数a释放了它所占用的内存,所以函数a不会造成内存泄漏

函数b中的变量obj是引用类型(Object),当调用函数b时,将obj的地址值赋值给变量d,调用完函数b后,函数b释放了它所占用的内存,但0x0003的内存却没有被释放,因为全局变量d引用了0x0003,它是一个可达的对象。

function a(){
    let obj1=new  Object();
    let obj2=new  Object();
    obj1.a=obj2;
    obj2.a=obj1;
}

a();
 

引用清除2.jpg

调用函数a完后,全局上下文中没有变量引用它们,所以它们是不可达对象,等下次垃圾回收程序运行时,就会回收它们的内存。

function a(){
    let obj1=new Object();
    let obj2=new Object();
    obj1.a=obj2;
    obj2.a=obj1;
    return{
        aa:obj1,
        bb:obj2,
    }
}

var f=a();
 

扩展.jpg

扩展2.jpg

调用函数a完后,全局上下文中变量f引用了它们,所以它们是可达对象,垃圾回收程序不会回收它们。

function a(){
    let obj1={
        name:"小黄"
    }
    let obj2={
        name:"小白"
    };
    obj1.a=obj2;
    obj2.a=obj1;
    return{
        aa:obj1,
        bb:obj2,
    }
}

var f=a();
f.aa=null;
 

大家想一想别名为b的对象会不会被垃圾回收程序回收?

扩展3.jpg

答案是不会!,可以通过别名为a的对象,到达别名为b的对象,再看下面的代码

f.bb.a=null;
 

扩展4.jpg

当把obj2.a=null时,全局上下文变量f再也到达不了别名为b的对象,所以等下次垃圾回收程序运行时,就会把别名为b的对象进行回收

到这里,我就把我自己对于垃圾回收机制的理解说完了,有什么不对的地方,欢迎大家指出^^

回复

我来回复
  • 暂无回复内容