内存管理是计算机软件开发的重要组成部分,负责应用程序中内存的有效分配、利用和释放。其重要性在于增强软件性能,保证系统稳定性。
垃圾收集 (GC) 在 Java 和 Go 等当代编程语言中至关重要。它自动检测并回收未使用的内存,从而减轻开发人员手动管理内存的需要。 GC 的概念最初出现在 20 世纪 50 年代末的 LISP 编程语言中,标志着自动内存管理的引入。
自动化内存管理的主要优势包括:
了解内存中“垃圾”的本质并识别可回收空间至关重要。在接下来的章节中,我们将从探索垃圾收集的基本原理开始。
引用计数算法在对象标头中分配一个字段来跟踪其引用计数。此计数随着每个新引用而增加,并在删除引用时减少。当计数达到零时,该对象就有资格进行垃圾回收。
考虑以下代码:
首先创建一个由d引用的值为demo的字符串(图1)。
String d = new String("demo");
图 1 – 创建字符串后
然后,将 d 设置为 null。 demo的引用计数为零。在引用计数算法中,需要回收demo的内存(图2)。
d =null; // Reference count of 'demo' becomes zero, prompting garbage collection.
图 2 – 当引用无效时
引用计数算法在程序执行期间运行,避免了Stop-The-World事件,该事件会暂时停止程序以进行垃圾收集。然而,它的主要缺点是无法处理循环引用(图 3)。
例如:
public class CircularReferenceDemo { public CircularReferenceDemo reference; private String name; public CircularReferenceDemo(String name) { this.name = name; } public void setReference(CircularReferenceDemo ref) { this.reference = ref; } public static void main(String[] args) { CircularReferenceDemo objA = new CircularReferenceDemo("Ref_A"); CircularReferenceDemo objB = new CircularReferenceDemo("Ref_B"); objA.setReference(objB); objB.setReference(objA); objA = null; objB = null; } }
这里,尽管使外部引用无效,但 objA 和 objB 之间的相互引用阻止了它们的垃圾回收。
图 3 – 循环引用
我们可以看到这两个对象都无法再访问了。然而,它们是互相引用的,因此它们的引用计数永远不会为零。因此,GC 收集器永远不会被通知使用引用计数算法对它们进行垃圾收集。
该算法实际上是通过使用 std::shared_ptr 在 C 中实现的。 std::shared_ptr 旨在管理动态分配对象的生命周期,在创建或销毁指向对象的指针时自动增加和减少引用计数。该智能指针是 C 标准库的一部分,提供强大的内存管理功能,可显着降低与手动内存处理相关的风险。每当复制 std::shared_ptr 时,托管对象的内部引用计数就会增加,反映新的引用。相反,当 std::shared_ptr 被破坏、超出范围或重新分配给不同的对象时,引用计数就会减少。当引用计数为零时,分配的内存会自动回收并销毁对象,
通过确保没有必要时不会保留分配的对象,有效防止内存泄漏。
可达性分析算法从 GC 根开始,遍历对象图。无法从这些根到达的对象被视为不可恢复,并成为收集的目标。
如下图所示,蓝色圆圈内的对象应该保持存活,灰色圆圈内的对象可以回收(图4)。
图 4 – 内存泄漏
该方法有效解决了引用计数算法固有的循环引用问题。从 GC 根无法到达的对象将被分类进行收集。
通常,被视为 GC 根的 Java 对象包括:
GraalVM 提供了一个提前 (AOT) 编译器,它将 Java 应用程序转换为独立的可执行二进制文件,称为 GraalVM Native Images。这些二进制文件由 Oracle 实验室开发
封装应用程序和库类以及 GC 等运行时组件,允许在没有 Java 运行时环境 (JRE) 的情况下进行操作。
该过程涉及静态分析以确定可访问的组件、通过执行块进行初始化,以及通过创建应用程序状态快照以供后续机器代码翻译来完成。
Substrate VM 是 GraalVM 套件的一个组成部分,由 Oracle 实验室精心策划。它是一个增强的 JVM,不仅支持提前 (AOT) 编译,而且还有助于执行 Java 以外的语言,例如 JavaScript、Python、Ruby,甚至是 C 和 C 等本机语言。 Substrate VM 的核心是一个复杂的框架,允许 GraalVM 将 Java 应用程序编译成独立的本机二进制文件。这些二进制文件不依赖于传统的 Java 虚拟机 (JVM) 来执行,这简化了部署和
操作流程。
Substrate VM 的主要功能之一是其专门的垃圾收集器,它针对需要低延迟和最小内存占用的应用程序进行了微调。该垃圾收集器擅长处理与本机映像不同的独特内存布局和操作模型,这与在标准 JVM 上运行的传统 Java 应用程序有很大不同。 Substrate VM 本机映像中缺少即时 (JIT) 编译器是一种战略选择,有助于最小化可执行文件的总体大小。这是因为它消除了包含 JIT 编译器和相关元数据的必要性,这些元数据的大小和复杂性都很大。
此外,虽然 GraalVM 是使用 Java 开发的,但这会带来一定的限制,特别是在本机内存访问方面。此类限制主要是出于安全考虑以及保持跨平台兼容性的需要。然而,访问本机内存对于优化垃圾收集操作至关重要。为了解决这个问题,Substrate VM 采用了一套专门的接口来促进与本机内存的安全高效的交互。这些接口是更广泛的 GraalVM 架构的一部分,使 Substrate VM 能够以类似于 C 等较低级语言的方式有效管理内存,同时保留 Java 的安全性和可管理性。
在实践中,这些功能使 Substrate VM 成为一种极其通用的工具,可以增强使用 GraalVM 编译的应用程序的功能和效率。通过允许开发人员
Substrate VM 利用更广泛的编程语言并将其编译为高效的本机二进制文件,突破了传统 Java 开发环境所能实现的界限。这使其成为需要高性能、减少资源消耗和多种语言支持的现代软件开发项目的宝贵资产。
Substrate VM 值得注意的元素包括:
通过用于原始内存操作的指针接口指针和用于处理字大小值的 WordBase 接口 WordBase 等接口简化内存访问。
将堆划分为包含不可变对象的预初始化段和用于动态对象分配的运行时段(图 5)。
图 5 – 本机映像中的内存管理
在运行时,Substrate VM 中所谓的镜像堆包含在镜像构建过程中创建的对象。堆的这一部分是用可执行二进制文件的数据部分中的数据预先初始化的,并且可以在应用程序启动时轻松访问。驻留在图像堆中的对象被认为是不朽的;因此,这些对象中的引用被
视为根指针
垃圾收集器。但是,GC 仅扫描部分映像堆中的根指针,特别是那些未标记为只读的指针。
在构建过程中,指定为只读的对象被放置在映像堆的特定只读部分中。由于这些对象永远不会保存对运行时分配的对象的引用,因此它们不包含根指针,从而允许 GC 在扫描期间绕过它们。同样,仅由原始数据或原始类型数组组成的对象也缺少根指针。此属性进一步简化了垃圾收集过程,因为这些对象可以从 GC 扫描中省略。
相反,Java 堆被指定用于保存在运行时动态创建的普通对象。堆的这一部分会定期进行垃圾收集,以回收不再使用的对象占用的空间。它被构造为具有老化机制的分代堆,随着时间的推移促进高效的内存管理。
预初始化、永久映像堆和动态管理的 Java 堆之间的这种划分使 Substrate VM 能够优化内存使用和垃圾收集效率,满足应用程序内存需求的静态和动态方面。
在 Substrate VM 的堆模型中,内存被系统地组织成称为堆块的结构。这些块的默认大小通常为 1024KB,形成一个连续的虚拟内存段,仅分配给对象存储。这些块的组织结构是一个链表,其中尾部块代表最近添加的段。这样的模型
促进高效的内存分配和对象管理。
这些堆块进一步分为两种类型:对齐和未对齐。对齐的堆块能够连续保存多个对象。这种对齐允许更简单地映射
对象到各自的父堆块,使内存管理更加直观和高效。在需要对象提升的场景中 - 通常是在垃圾收集期间和
内存优化——对象从其在父堆块中的原始位置移动到位于指定的“旧目标空间”中的目标堆块。此迁移是分代堆管理策略的一部分,该策略通过将年轻对象与旧对象分离来帮助优化垃圾收集过程,从而减少 GC 周期期间的开销。
GraalVM Native Image支持针对不同需求定制的各种GC:
串行 GC: 适用于单线程应用程序的默认低占用空间收集器。
G1 垃圾收集器: 专为具有大堆大小的多线程应用程序而设计,增强了生成管理的灵活性。
Epsilon GC: 一种简约的收集器,处理分配但缺乏回收,最适用于可预测堆利用率的短期应用程序。
总而言之,Substrate VM 通过结合专门的垃圾收集和结构化堆管理等先进技术,有效地优化了 GraalVM 中的内存管理。这些功能(包括堆块以及用于图像和 Java 堆的单独内存段)可简化垃圾收集并提高应用程序性能。由于 Substrate VM 支持多种编程语言并将其编译为高效的本机二进制文件,因此它展示了现代 JVM 框架如何超越传统边界,以提高不同应用程序环境中的执行效率和鲁棒性。这种方法为虚拟机技术和应用程序部署的未来发展设定了高标准。
免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。
Copyright© 2022 湘ICP备2022001581号-3