我想在java脚本中询问有关标记和扫描的问题,我将在下面提供该代码
var user = "mina";
var user = null;
console.log(user);
Run Code Online (Sandbox Code Playgroud)
在该代码中,如果我们实现标记并扫描"var user ="mina""被渗透到垃圾,因为它不再可访问是正确的
免责声明:我在微软工作时参与了Chakra JS引擎.
第一:你的问题预先假定ECMAScript(JavaScript的规范)要求实现使用Mark-and-Sweep Garbage Collector,相反:它不会:
ECMAScript规范不需要垃圾收集,实际上根据我对规范的解释,实现永远不会释放内存并且最终无法再分配并且崩溃可怕的实现仍然是有效的实现.即使语言规范确实需要GC(如.NET的公共语言规范),它们也没有规定必须使用Mark-and-Sweep策略.
历史旁注:我认为JScript和VBScript的Active Scripting引擎(早于Chakra的JIT引擎)使用了COM对象和环境内在函数的引用计数,而JavaScript值(字符串和JS对象)使用了某种形式的标记和扫描但是我可能错了,因为我从来没有在那个引擎上工作过.Chakra使用更高级的技术,包括按字符串传递小字符串,我不会在这里详细介绍,但Chakra的源代码可以在GitHub上找到:https://github.com/Microsoft/ChakraCore
关于使用string值的具体问题:由于字符串文字是不可变的,因此在编译或解释代码之前,它们往往被脚本解析器实现,因此当字符串丢失引用时,其字符串数据(字符数组)将不会被收集或重新分配,但只是留在记忆中.如果它是运行时创建的字符串,那么它最终将被释放,而不一定在引用丢失的确切时刻(与简单的引用计数实现一样).
关于你的确切代码:因为该“mina”值实际上从未使用过,否则一个合适的代码优化器会完全删除该行,因此不会有任何空闲(假设该字符串未被实现).
忽略那些"失去所有引用后对象会发生什么"的技术性,那取决于JavaScript引擎使用的GC类型或自动内存管理:
让我们使用这个例子:
function doSomething() {
var nihilist = {
name: 'Friedrich Nietzsche',
dob: new Date( '1844-10-15' )
};
console.log( nihilist.name );
nihilist = null;
}
Run Code Online (Sandbox Code Playgroud)
不同的JavaScript或ECMAScript引擎可以自由地实现内存管理,但是他们认为合适(严格来说,包括什么都不做).那么让我们考虑一些常见场景中会发生什么:
一个分析对象及其引用的生命周期的智能编译器 (并且知道console.log没有副作用)会看到为该对象创建的对象nihilist永远不会被该函数暴露(即它不是return'd,它没有分配给某些一种输出参数,没有隐藏的async状态机或对象的唯一引用的闭包捕获),因此它不需要nihilist在自由存储中分配对象(无论它是堆,竞技场等)并且可以将它放在调用堆栈上,所以当nihilist = null重新分配时,原始对象值仍然存在于堆栈中,但是当doSomething返回时它将被释放(假设当然console.log没有存储对name字符串的引用).
nihilist对象X在堆栈中创建.X被分配给nihilist参考.nihilist = null被分配,没有任何反应X(比失去其最后一个引用除外).doSomething返回并且堆栈指针移动到前一个堆栈帧,并且X当调用堆栈有更多帧被推到它上时(例如通过另一个函数调用),将覆盖包含的内存.X在免费商店中创建一个新对象.其引用计数为零.内部name string值的引用计数设置为1(假设它没有实现).X被分配给nihilist引用,其计数增加到1.console.log被称为传递nihilist.name,这增加Y的引用计数2,然后返回到1时console.log返回(假设console.log无新引用它).nihilist = null已分配,并且X参考计数降至零.nihilist = null释放内存后立即执行.有不同的方法来实现跟踪收集器 - 其中一个策略是你提到的天真的标记和扫描策略:
关于追踪垃圾收集的重要一点是,运行时需要某种方式已经知道分配对象在内存中的位置,以及跟踪对象之间引用的方法.这在JavaScript中更容易,其中引用不是简单的32或64位原始指针,而是更可能struct包含大量元数据的相当大的C 对象 - 并且所有对象分配都可以存储在"对象表"中以进行简单迭代(这称为"精确的垃圾收集"或"确切的垃圾收集"); 其他方法涉及启发式扫描原始内存以查找看起来像指针的值.
需要注意的另一个重要的事情是,跟踪的GC一般不会在程序中的特定点运行,或由程序直接调用,而不是GC在后台线程中运行,并冻结程序执行时,它希望(这被称为"停止"并且通常是为了响应增加的内存使用量,也可能是在定时器间隔时间",然后执行其收集,并且只有在完成时才恢复其他线程.这种情况不可预测地发生,这就是追踪GC系统无法在硬实时环境中使用的原因.
在这种情况下,我们假设我们的示例跟踪GC JavaScript环境使用精确垃圾收集(我注意到Chakra主要使用内存扫描技术).
X在免费商店中创建.其内存地址和大小将添加到运行时中的已知对象列表中.1.1.它也引用了name字符串(我们假设它是一个实例化的不可变字符串Y).1.2.该dob: new Date对象将被存储的值内X(原记忆有一个"标签",告诉它是一个在rutime Date存储的价值,但它可以改变一个Date`参考后面.var nihilist = X赋值之后,X变为与表示函数局部变量的特殊GC根相关联(因为变量本身不是对象),即" X可达".如果X被另一个对象引用Z,那么该对象将被根引用,并且X将是2度分离但仍可到达.console.log将在临时引用内部Y结束console.log.因为console.log没有提到X它意味着如果console.log 确实做了长期的参考Y,X仍然可以安全地销毁.nihilist = null被分配,然后X是没有再访问,但什么都不会立即发生:即内存X占用和有关X的配置元数据保持不变.在未来的某个时刻(可能会立即或可能是几分钟甚至几小时)GC将冻结程序执行并开始其标记和扫描:
5.1.首先,它遍历其根对象(包括表示局部变量的特殊根)并将它们注释为仍然存活(存储注释的内存可以就地存在(例如,每个对象都有一个存储其死/活的元数据头) state)或者它可以在步骤1)中提到的known-object-list中,例如:
function checkObject( allocatedObject ) {
if( allocatedObject.status == UNKNOWN ) {
allocatedObject.status = ALIVE;
foreach( reference in allocatedObject.references ) {
reference.destination.status == ALIVE;
checkObject( reference.destination );
}
}
}
foreach( root in allRoots ) {
foreach( reference in root.references ) {
checkObject( reference.destination );
}
}
Run Code Online (Sandbox Code Playgroud)
5.2.然后它遍历非根对象列表(allNonRootObjects)并检查它们是否存活:
foreach( allocatedObject in allNonRootObjects ) {
if( allocatedObject.status == UNKNOWN ) {
deallocate( allocatedObject );
}
}
Run Code Online (Sandbox Code Playgroud)| 归档时间: |
|
| 查看次数: |
93 次 |
| 最近记录: |