想要理解垃圾回收机制我们需要通过3W原则,what why how来学习这一概念
什么是垃圾回收机制
一个程序的运行需要内存来存放,一个持续运行的服务在运行时(runtime)可能需要不断地使用新的内存来存放数据,同时也要将不再使用的内存释放,而垃圾回收机制就是一个用来将不再需要的内存进行回收。它是一个大多数语言所提供的自动内存管理,不需要程序员手动使用free之类的操作来释放。
引用计数(reference counting)
这是一种不太常用的垃圾回收方式,它的实现依靠引用技术这个策略,语言引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。通过跟踪记录每一个值被使用的次数,当一个声明的变量将一个引用类型赋值给该变量则这个值的引用次数就加上1,当这个变量的值发生改变,则这个值的引用次数就-1,当值的引用次数为0时,垃圾回收机制在执行时就会将他清除(垃圾回收机制是周期性执行,保证性能与资源占用的平衡)
但是引用技术有一个局限性,对象A中的某个属性指向对象B,对象B中的某个属性指向对象A就会出现循环引用,(当然不止这一种情况,不过原理是一样的),也就是出现循环引用的情况
let objA = {
name:father,
mate:objB
}
let objB = {
name:mather,
mate:objA
}
当 objA 这个变量执行 objA 这个对象时,objA 这个对象的引用计数会加 1,此时引用计数值为 1,接下来 objB 的 mate 属性又指向了 objA 这个对象,所以此时 objA 这个对象的引用计数为 2。同理 objB 这个对象的引用计数也为2.
就算此时将 objA 和 objB 设置为null,但是这两个对象的引用计数都-1,并不为 0,所以并不会进行垃圾回收,但是这两个对象已经没有作用了,在函数外部也不可能使用到它们,所以这就造成了内存泄露。
此时需要一个天降猛男来解决这个问题。也就是标记清楚法来处理这个垃圾回收机制。
可达性(Reachablility)
在描述标记清除前还需要知道一个概念就是可达性,可达性和可达值是绑定在一起的概念。
垃圾回收机制通过对引用可达性的描述来判断需不需要释放这块内存。
在整个runtime内会有一些值常规下不会被释放,当一个值可以通过引用或引用链来访问到时则判断该值为可达的。
使一个对象被user引用,则这个对象则是可以通过全局到user到对象这条链上的引用找到,当user=null时,user到这个对象的指向就断开,则object就会因为失去可达性被下一次垃圾回收机制所释放。
当object被user和admin同时引用时断了一条引用,还能通过admin可达,则object还是具备可达性的。
可以理解为当没有引用箭头指向自己时,也就是入度为0时,也就是它被释放的时候,这时候是不是想到了一个很熟悉的概念呢,那就是图的遍历,一个确定从global出发的引用链,当一步一步遍历入度为0的图 遇到与global不连通的值则是需要被释放的。
标记清除 (mark and sweep)
最开始的时候将所有的变量加上标记,当执行函数的时候会将函数内部的变量这些标记清除,在函数执行完后再加上标记。这些被清除标记又被加上标记的变量就被视为将要删除的变量,原因是这些函数中的变量已经无法被访问到了。像上述代码中的 obj1 和 obj2 这两个变量在刚开始时有标记,进入函数后被清除标记,然后函数执行完后又被加上标记被视为将要清除的变量,因此不会出现引用计数中出现的问题,因为标记清除并不会关心引用的次数是多少。
function speakLines(){
let night="天黑";//做个标记 ,进入环境
let closeEyes="闭眼";//做个标记 ,进入环境
let speak=`开始狼人杀,${night}请${closeEyes}`;//做个标记 ,进入环境
console.log(speak);
}
speakLines() //代码执行完毕 里面被标记过的变量,又被标记 离开环境 最后被回收
当代码执行在一个环境中时(例如上面的 speakLines函数),每声明一个变量,就会对该变量做一个标记(例如标记一个进入执行环境);当代码执行进入另一个环境中时,也就是要离开上一个环境( speakLines 函数执行完毕,去执行其他的函数),这时对上一个环境中的变量做一个标记,(例如标记一个离开执行环境),等到垃圾回收执行时,会根据标记来决定要清除哪些变量进行释放内存