垃圾和孤岛!

对于大多数新程序员来说,垃圾回收是最大的谜。 特别是因为Sun / Oracle设法将垃圾收集器的各个方面很好地抽象出来。 这最终使刚受到业界欢迎的程序员为系统内部编写不良代码。 本文是有关垃圾收集和称为隔离岛的概念的简要概述

二十,三十年前,记忆就是一切。 没有Java(在95年就引入了Java)和Python,因此您必须管理密集型桌面应用程序或复杂算法的内存。 嗯,它们可能并没有那么复杂,因为您可能无法使用当时存在的机器来运行复杂的算法模型。 事情肯定已经发生了变化,但是摩尔定律已经死了,因为多年来记忆的发展滞后。 因此,即使在今天,管理内存也是至关重要的,但与之前我们不得不进入汇编级代码以找出内存泄漏的情况相比,它仍然至关重要。

为了了解垃圾收集的核心,您需要一点系统架构概念。 因此,我们现在将对其进行审查。

为了编写程序,必须使用变量,因此,需要访问内存以存储值。 例如,使用此for循环。

  int i = 0; i <n; ++ i){ 
std :: cout << i << std :: endl;
}

这只是一个迭代从0到n的整数集的简单循环。 因此,聪明的程序员必须注意存储器的某些关键方面。

考虑变量i 。 需要根据指令++i进行递增。 为了增加它,程序需要记住 i的先前值,从而引起对存储器或寄存器的需求。

编程语言不包括这些变量,就像i只是在内存中处于随机位置一样-恰恰相反-它们具有用于分配变量的特定范例-组织良好的范例。 每个运行时都有自己的堆栈,在该堆栈中加载了函数,每个函数在堆栈本身内部都嵌入了自己的堆栈框架。 当函数完全执行后,与该特定函数有关的框架会与所有变量,返回值和链接一起弹出堆栈。

因此,此变量i存储在此特定执行功能的堆栈框架内。 也许这是main方法,或者是main方法本身内部运行的子方法。 在这种情况下,main方法将保留其堆栈指针,直到子功能完成执行为止,并且在完全执行子功能并从堆栈中弹出后,将返回其堆栈指针值。

随着编程范例的不断发展,OOP的新范例与类和对象一起发挥了作用,这带动了垃圾回收的兴起。 实例化的每个对象(类的实例)都分配到堆中。 这是因为用户无需担心对象的生存时间,因为堆是垃圾收集器执行工作的地方。

那么为什么要垃圾回收呢? 摆脱堆中的内存泄漏至关重要。 请注意这个显示引用的重新分配的经典示例。

 公共无效trashCollectionEx(){ 
对象o = new Object();
对象p = new Object();
o =空;
}

o引用的对象在运行时不再可访问。 因此,垃圾收集器将伸出并抓住该特定对象并将其从堆中删除。 然后,内存管理会注意减少GC留下的碎片。

请注意,不需要垃圾收集堆栈变量,因为在返回函数时,所有变量都会弹出,因为会弹出属于该特定函数的堆栈框架。 为了您的方便,我将在此处添加函数的堆栈框架。 这就是通常在书本中描绘堆栈框架的方式。

考虑上述情况。 那里有两个名为AB的对象,每个对象都在某个地方实例化。 这些对象由增变器创建的参考变量引用 增幅器是一个程序,它可以创建,更新和删除对象。

现在说我们使z无效。 因此,我们将无法从z到达A。 但是,仍然可以通过k到达A。 我们通过k访问B 然后可以通过y到达A。 因此, A仍然可以到达。

现在,为了论证,让我们使k无效

请注意,我们现在无法到达 AB。

这种现象称为隔离岛。 隔离孤岛中的对象可以进行垃圾收集。

  IslandOfIsolation类{ 
A z = new A();
B k =新的B();
zy = k;
kx = z;
k = z = null;
//以前由z和k引用的对象现已隔离。
} A级{
B y;
} B级{
A x;
}

隔离岛是达到对象GC资格的非常独特的情况。 但是,实际上,我们如何确定其他情况下对象的可达性 ? 为了利用这一点,有两个概念被利用,它们被称为引用计数基于跟踪的垃圾收集。 基于跟踪的GC将在另一篇文章中讨论。

留意到目前为止我们已经讨论过的关于可达性的四个观点。 那些是,

  1. 对象分配。
  2. 参数传递
  3. 参考作业
  4. 程序返回

对象分配

实例化对象时,该特定对象的引用计数增加1。

参数传递

例如,假设我们将对象传递给方法。 在此,将输入参数分配给方法签名本身的形式参数,因此,参考计数增加一。 这是因为即使我们也分配了形式参数来引用该特定对象,该对象仍然可以访问。

参考作业

这就像为对象分配另一个参考变量。

 对象p = new Object(); 
对象o = new Object(); p = o;

o所引用的对象的引用计数在此递增1,而p所引用的旧对象的引用计数则递减1。这很容易理解,因为现在p将指向o所引用的对象为好。 由p引用的旧对象不再具有引用(在此上下文中)。

程序返回

在过程返回(方法返回)时,必须减少该特定激活记录(堆栈帧)的局部变量所保存的所有引用计数。 考虑到我们在返回过程(弹出堆栈)时无法访问局部变量的事实,这也是微不足道的。

可及性的传递性损失

每当可到达性(引用计数)变为零(0)时,我们都必须递减从先前对象可传递到达的对象的所有引用计数。 也就是说,例如,如果A代表B和C,而B代表D,则当A出现零可及性时,B,C和D的参考计数应减少。

达到0参考计数后,GC将收集对象。

众所周知,引用计数GC非常昂贵,因为它应该保留原子引用计数。 管理此计数会产生大量的间接费用。

再进一步说明一下,由于C / C ++等语言的类型不安全 ,因此它们不应实现自动GC,即可以进行类型转换,而不会出现任何异常或错误。 例如,请考虑以下代码片段。

  char arr [5]; 
arr [5] ='d';

以上陈述在语法上是正确的。 它不违反任何词汇介词。 但是,直觉说这是不正确的,因为它将对数组产生某种越界错误。 但是在像C / C ++这样的语言中,这类语句被认为是毫无意义的。 它们不会导致错误或引发异常。

但是在类型安全的语言中,这些会产生错误。 如果您使用Java输入上述代码段,则会得到IndexOutOfBoundException

由于C / C ++语言的类型不安全,因此可以使用指针强制转换来使用指针进行进一步的计算-这使我们想到了关于类型不安全语言的GC的一个关键事实。 假设您有一个指向堆中某个特定内存位置的指针,并且对该特定指针进行了一些指针运算。 一旦这样做,由于现在您的指针指向另一个内存位置,因此前一个对象可以访问GC。 但是,如果现在反转指针算法,则会得到空指针异常,因为位于先前通过指针指向的内存位置中的对象不再存在,因为GC收集了该对象。 因此,在使用指针的语言中拥有自动GC非常危险-通常,您需要使用这些类型的语言进行手动垃圾回收。 但是,现在有了诸如智能指针之类的新概念,使程序员更容易使用GC。

资源—编译器,原理和技术(第二版)(Dragon Book)