我想问一个关于Java脚本中标记和清除的问题,我将在下面提供该代码
var user = "mina";
var user = null;
console.log(user);
在该代码中,如果我们实现标记并清除“ var user =” mina“”,则会渗入垃圾,因为它不再可访问了,这是正确的
最佳答案
免责声明:在Microsoft期间,我曾研究过Chakra JS引擎。
首先:您的问题以ECMAScript(JavaScript规范)为前提,要求实现使用标记清除垃圾收集器,相反,它不是:
ECMAScript规范不需要垃圾收集,实际上,根据我对规范的理解,永远不会释放内存并最终无法分配更多资源并导致严重崩溃的实现仍然是有效的实现。即使语言规范确实需要GC(例如.NET的通用语言规范),他们也没有规定必须使用标记并扫描策略。
历史的旁注:我相信JScript和VBScript的Active Scripting引擎(早于Chakra的JIT引擎)使用了COM对象和环境内在函数的引用计数,而JavaScript值(字符串和JS对象)使用了某种形式的标记扫掠,但我可能从未在该引擎上工作过,所以可能是错的。 Chakra使用更高级的技术,包括按值传递小字符串,我在这里不做详细介绍,但是Chakra的源代码可在GitHub上找到:https://github.com/Microsoft/ChakraCore
关于使用string
值的特定问题:由于字符串文字是不可变的,因此在代码甚至被编译或解释之前,脚本解析器通常会将它们设为interned,因此当字符串丢失引用时,它的字符串数据(字符数组)将不会被收集或重新分配,而只是保留在内存中。如果它是运行时创建的字符串,那么它将最终被释放,而不必在引用丢失的确切时刻被释放(与琐碎的引用计数实现一样)。
关于您的确切代码:由于实际上从未使用过“mina”
值,因此,一个体面的代码优化器会完全删除该行,因此不会有任何空闲空间(假设未对字符串进行内插)。
忽略这些技术上的“对象丢失所有引用后会发生什么”,然后取决于GC的类型或JavaScript引擎使用的自动内存管理:
让我们使用这个例子:
function doSomething() {
var nihilist = {
name: 'Friedrich Nietzsche',
dob: new Date( '1844-10-15' )
};
console.log( nihilist.name );
nihilist = null;
}
不同的JavaScript或ECMAScript引擎可以自由地实现内存管理,但是它们认为合适(严格来说,包括什么也不做)。因此,让我们考虑一下在某些常见情况下会发生什么:
堆栈分配
A smart compiler that analyses the lifetime of objects and their references(并且知道
console.log
没有副作用)会看到为nihilist
创建的对象永远不会被该函数公开(即,它不是return
的,也没有分配给某些对象)这类输出参数,没有隐藏的async
状态机或对象唯一引用的闭包捕获),因此不需要在自由存储区中分配nihilist
对象(无论是否是堆) (例如竞技场等)and could put it on the call-stack,因此当重新分配nihilist = null
时,原始对象值仍存在于堆栈中,但是当doSomething
返回时它将被释放(假设console.log
没有存储对< cc>字符串,当然)。在堆栈中创建
name
对象nihilist
。X
被分配给X
引用。分配
nihilist
时,nihilist = null
不会发生任何事情(除了丢失其最后一个引用)。X
返回并且堆栈指针移至上一个堆栈帧,并且当或如果调用堆栈中有更多帧(例如通过另一个函数调用)推入时,包含doSomething
的内存将被覆盖。参考计数
在免费商店中创建一个新的对象
X
。其参考计数为零。内部的X
name
值将其参考计数设置为1(假设它没有被interned)。string
分配给X
引用,并且其计数增加到nihilist
。1
被称为传递console.log
,这将nihilist.name
的引用计数增加到Y
,然后在2
返回时返回到1
(假设console.log
没有对其进行新引用)。分配了
console.log
,并且nihilist = null
的引用计数降至零。通常,引用计数系统会在对象计数降为零时立即立即释放对象的内存(尽管系统可能由于某些原因而延迟分配内存,但这超出了我的回答范围),因此在
X
内存之后被释放。追踪垃圾收集
有多种方法可以实现跟踪收集器-这些策略之一是您提到的朴素的标记扫掠策略:
关于跟踪垃圾收集要记住的重要一点是,运行时既需要某种方式来知道已分配对象在内存中的位置,又需要一种遵循对象之间的引用的方式。在JavaScript中,引用不是简单的32或64位原始指针,而是包含大量元数据的较大C
nihilist = null
对象,这很容易,并且所有对象分配都可以存储在“对象表”中以进行简单迭代(此操作被称为“精确垃圾收集”或“精确垃圾收集”);其他方法包括启发式扫描原始内存以查找类似于指针的值。另一个需要注意的重要事项是,跟踪GC通常不会在程序中的特定点运行,也不会直接由程序调用,而是在后台线程中运行,并在需要时冻结程序执行(这称为“停止运行”)。世界”,通常是为了响应增加的内存使用情况(可能也是在计时器间隔),然后执行其收集操作,并在完成后才恢复其他线程。这是无法预料的,这就是为什么无法在硬实时环境中使用Tracing GC系统的原因。
在这种情况下,我们假设示例Tracing GC JavaScript环境使用“精确垃圾收集”(我注意到Chakra主要使用内存扫描技术)。
新对象
struct
在免费存储中创建。它的内存地址和大小将添加到运行时中的已知对象列表中。1.1。它也引用了
X
字符串(我们假定是一个固定的不可变字符串name
)。1.2。
Y
对象将按值存储在dob: new Date
内部(在原始内存中,有一个“标签”告诉rutime它是X
存储的按值,但是可以将其更改为Date的引用)稍后的。在
Date
分配后,var nihilist = X
与代表函数局部变量(因为变量本身不是对象)的特殊GC根相关联,即“ X
可达”。如果X
被另一个对象X
引用,则该对象将由根引用,并且Z
将是2个分离度,但仍然可以访问。在
X
内部将临时引用console.log
,该引用在Y
返回时结束。因为console.log
不引用console.log
,这意味着,如果X
确实对console.log
作了长期引用,则Y
仍然可以安全地销毁。分配
X
时,将无法再访问nihilist = null
,但将不会立即发生任何事情:X
占用的内存和有关X的分配元数据保持不变。在将来的某个时间点(可能是立即发生,甚至可能是几分钟甚至几小时的路程),GC都会冻结程序执行并开始进行标记扫掠:
5.1。首先,迭代其根对象(包括表示局部变量的特殊根)并将其注释为仍然有效(存储注释的内存可以就位(例如,每个对象都有一个元数据头,用于存储其无效/有效)状态),也可以在步骤1)中提到的已知对象列表中,例如:
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 );
}
}
5.2。然后,它遍历非根对象(
X
)的列表,并检查它们是否存在: foreach( allocatedObject in allNonRootObjects ) {
if( allocatedObject.status == UNKNOWN ) {
deallocate( allocatedObject );
}
}
关于javascript - 该代码中标记和清除的结果是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54173974/