- 作者:老汪软件技巧
- 发表时间:2024-09-27 07:01
- 浏览量:
前言
当我们谈及垃圾回收机制就不得不提及在JavaScript中一个函数执行完毕,这个函数的执行上下文就会被销毁,从哪呢被销毁呢?是在调用栈中销毁的.我们来看看这样一个函数,它的打印结果会是什么呢?
function foo(){
var a = 1;
var b = a;
a = 2;
console.log(a);
console.log(b);
}
foo()
我们都知道,这一份代码被V8引擎读到时,会先有一个预编译在执行的过程.此时预编译就会创建一个函数的执行上下文对象.
假设这个红色的框是V8的调用栈,在v8开始工作那一刻就会有一个全局执行上下文入栈,我们全局声明了一个foo值是一个函数体,函数体是引用类型,此时就会创建一个堆结构,在这个堆结构中会存放这个foo的指针和函数名,这个foo的指针就会在全局执行上下文中,此时全局编译就结束了,就会开始全局执行.那么foo的执行上下文入栈(绿色边框,左边是变量环境,右边是词法环境),a是原始类型,就直接存在了变量环境中,b也同理,此时b的值就会是1,随后a的值会被修改成2,所以打印的值是2,1.
什么是垃圾回收
我们简单记忆就可以理解为当一个数据被使用完之后,这一份数据就被称之为垃圾数据,我们需要对这些垃圾数据进行回收,释放有限的内存.
垃圾回收有两种方式一种是手动回收,一种是自动回收.在C或者C++中使用的就是手动回收
char* p = (char*)malloc(2048)
free(p)
p = NULL
我们看这样一个简单的片段,我们创建了一个指针存放了2048个字节,我们必须使用free()去手动释放掉这个指针p所占有的内存,如果不手动回收就会让有限的内存变小,出现内存泄漏这一问题.
那么自动回收在JavaScript,Java,Python中就会存在.下面我们看看这样一份代码;
function foo(){
var a = 1;
var b = {name:'张三'};
function showName(){
var c = 2;
var d = {name:'李四'}
}
showName()
}
foo()
它的执行上下文会长什么样呢?
当foo执行完毕,那么此时foo的执行上下文就需要被销毁,那么foo执行上下文是如何被销毁的呢?在调用栈中有一个指针esp记录当前执行状态的指针.如果执行到了showName那么此时v8就会创建showName的执行上下文,与此同时esp就会指向showName的执行上下文,表示此时正在执行showName.接着showName执行完毕就会继续来到foo,那么此时esp就会指向foo的执行上下文,那么此时esp已经走过了showName的执行上下文,V8就会认为showName的执行上下文为无效内存.当esp指向全局时,调用栈里面所占据的内存释放完毕了,但是在堆中依旧存在数据,这个堆中的内存该如何释放呢?
堆内存回收
V8的堆可分为两个区域:新生代和老生代.新生代区域通常用于存放生存时间短的对象,老生代区域通常用于存放生存时间长的对象.新生代分配的内存大约在1~8M而老生代区域分配的内存却很大.而且新生代区域的垃圾回收器叫做副垃圾回收器,老生代区域的垃圾回收器称之为主垃圾回收器.
垃圾回收器的工作流程是这样的:
第一步:先标记空间中的活动对象(正在使用)和非活动对象(已经用完了)
第二步:回收非活动对象的内存
第三步:内存整理
这里内存整理是指在执行完垃圾回收之后,内存中可能会出现许多不连续的空闲空间,这会导致内存碎片化,那么内存整理是解决这个问题的.
下面我们就讲讲新生代区域和老生代区域:
在堆内存中被分为新生代区域和老生代区域,新生代区域采用的是scavenge算法
在新生代区域部分被划分成两部分一部分是对象区域,一部分是空闲区域.最新创建的对象会被存入到对象区域,当对象区域快满了就会执行一次垃圾回收.当清理完一遍之后就会将存活的对象复制到空闲区域,就会进行内存整理,此时空闲区域就不会存在内存碎片.整理完毕之后,对象区域和空闲区域反转,对象区域变成空闲区域,空闲区域变成对象区域.在反转之前,对象区域会清空.
当新生代区域内存填满了,就会有一种策略叫做对象晋升策略.于是存活的对象(一般是两轮)通过对象晋级策略来到老生代区域.存在在老生代区域的对象的热点就是本身自己所占内存很大或者本身自己活的很久.
在老生代区域采用的是标记-清除法.当标记-清除结束也会进行一遍整理.
全停顿
什么是全停顿呢?全停顿是指当垃圾回收算法生效的时候, js逻辑的执行就要停止,等待垃圾回收完成后在执行。全停顿会造成页面卡顿。
解决办法:增量标记法.
总结
JavaScript中的垃圾数据回收机制是通过副垃圾回收器和主垃圾回收器来实现的。副垃圾回收器主要负责新生区的垃圾回收,采用Scavenge算法将新生代空间对半划分为对象区域和空闲区域,通过复制和角色翻转完成垃圾数据的回收。而主垃圾回收器则负责老生区的垃圾回收,采用标记-清除和标记-整理算法来处理大对象和长生命周期对象的回收。在执行垃圾回收时,由于JavaScript运行在主线程上,会导致全停顿现象,影响应用的性能和响应能力。为了降低全停顿的影响,V8引擎采用增量标记算法来分解垃圾回收任务,使其与JavaScript应用逻辑交替执行,从而减少页面卡顿现象