Go、Julia 和 Rust 等现代语言不需要像 Java C# 使用的那些复杂的垃圾收集器,为什么?
为了解释原因,我们需要了解垃圾收集器的工作原理以及不同语言如何以不同方式分配内存。然而,我们将首先看看为什么 Java 特别需要如此复杂的垃圾收集器。
我将在这里介绍许多不同的垃圾收集器主题:
- 为什么 Java 如此依赖快速 GC。我将介绍 Java 语言本身的一些设计选择,这些选择对 GC 造成很大压力。
- 内存碎片以及它如何影响 GC 设计。为什么这对 Java 很重要,但对 Go 则不那么重要。
- 值类型以及它们如何改变 GC 游戏。
- 分代垃圾收集器以及为什么 Go 不需要垃圾收集器。
- 逃逸分析——Go 用来减少 GC 压力的技巧。
- 压缩垃圾收集器——在 Java 世界中很重要,但不知何故 Go 避免了对它的需要。为什么?
- 并发垃圾收集——Go 如何通过使用多个线程运行并发垃圾收集器来解决许多 GC 挑战。为什么用 Java 做这件事要困难得多。
- 对 Go GC 的常见批评以及为什么该批评背后的许多假设通常有缺陷或完全错误。
为什么 Java 比其他人更需要快速 GC
Java 是一种基本上将内存管理完全外包给其垃圾收集器的语言。结果证明这是一个很大的错误。
为了解决这些主要缺点,Java 维护人员已经在高级垃圾收集器上投入了大量资金。这些做一些称为压缩的事情。压缩涉及在内存中移动对象并将它们收集到内存中的连续块中。这并不便宜。将块从一个内存位置移动到另一个内存位置不仅会消耗 CPU 周期,而且更新对这些对象的每个引用以指向新位置也会消耗 CPU 周期。
执行这些更新需要冻结所有线程。您无法在使用引用时更新引用。这通常会导致 Java 程序完全冻结数百毫秒,其中对象移动、引用更新和未使用的内存被回收。
现代语言如何避免与 Java 相同的陷阱?
现代语言不需要像 Java 和 C# 这样复杂的垃圾收集器。这是因为它们没有被设计为在相同程度上依赖它们。
<font><i>// Go: Make an an array of 15 000 Point objects in</i></font><font> type Point struct { X, Y <b>int</b> } <b>var</b> points [15000]Point </font>
在上面的 Go 代码示例中,我们分配了 15 000 个Point对象。这只是一个单一的分配,产生一个单一的指针。在 Java 中,这需要 15 000 个单独的分配,每个分配产生一个必须被管理的单独引用。每个Point对象都会获得我之前写过的 16 字节标头开销。在 Go、Julia 或 Rust 中,您都不会获得这种开销。对象通常是无标识的。
在 Java 中,GC 获得了 15000 个它必须跟踪和管理的单独对象。Go 只需要跟踪 1 个对象。
- 值类型
下面的代码定义了一个矩形,用 Min和Maxpoint 定义其范围。
type Rect struct { Min, Max Point }
这成为一个连续的内存块。在 Java 中,这将变成一个Rect对象,引用两个单独的对象,Min和Maxpoint 对象。
因此,在 Java 中,一个 的实例Rect需要 3 次分配,但在 Go、Rust、C/C++ 和 Julia 中仅需要 1 次分配。
Java 开发人员意识到他们搞砸了,并且您确实需要值类型以获得良好的性能。 Project Valhalla 是 Oracle 带头提供 Java 值类型的一项努力,
- 值类型还不够
Valhalla 会解决 Java 的问题吗?它只会让 Java 与 C# 处于同等地位。C# 在 Java 出现多年之后才出现,并且从那时起就意识到垃圾收集器并不是每个人都认为的那样神奇。因此,他们添加了值类型。
然而ÿ