热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

erlang垃圾回收_Erlang垃圾收集器

erlang垃圾回收卢卡斯拉尔森(LukasLarsson)这是对我们以前的博客文章Erlang19.0GarbageCollector的更新。使用Er

erlang 垃圾回收

卢卡斯·拉尔森 ( Lukas Larsson)

这是对我们以前的博客文章 Erlang 19.0 Garbage Collector的更新 使用Erlang / OTP 20.0,某些事情已经改变,这就是此更新的博客文章的原因。

Erlang通过跟踪垃圾收集器管理动态内存。 更准确地说,是使用Cheney的副本收集算法和全局大型对象空间的按进程生成的半空间副本收集器。

总览

每个Erlang进程都有其自己的堆栈和堆,这些堆栈和堆分配在同一内存块中,并且彼此接近。 当堆栈和堆相遇时 ,将触发垃圾收集器并回收内存。 如果没有回收足够的内存,堆将会增长。

建立资料

通过评估表达式在堆上创建术语。 术语有两种主要类型:不需要堆空间的立即数(小整数,原子,PID,端口ID等)和需要堆空间的利弊或盒装术语 (元组,大数,二进制等)。 即时术语不需要任何堆空间,因为它们被嵌入到包含结构中。

让我们看一个返回带有新创建的数据的元组的示例。

data(Foo) -> Cons = [42|Foo], Literal = {text, "hello world!"}, {tag, Cons, Literal}.

在此示例中,我们首先创建一个新的con单元格,该单元格带有整数和带有一些文本的元组。 然后,创建并返回一个大小为三的元组,将其他值包裹在一个atom标记上。

在堆上,元组的每个元素和标头都需要一个字长。 缺点单元格总是需要两个词。 将这些内容加在一起,我们得到的元组为7个单词,cons单元为26个单词。 字符串"hello world!" 是con单元格的列表,因此需要24个字。 原子tag和整数42不需要任何额外的堆内存,因为它是立即数 。 将所有术语加在一起,此示例中所需的堆空间应为33个字。

将此代码编译为梁装配体( erlc -S )可以准确显示正在发生的事情。

... {test_heap,6,1}. {put_list,{integer,42},{x,0},{x,1}}. {put_tuple,3,{x,0}}. {put,{atom,tag}}. {put,{x,1}}. {put,{literal,{text,"hello world!"}}}. return.

查看汇编代码,我们可以看到三件事: 如{test_heap,6,1}指令{test_heap,6,1}此函数中的堆要求仅为6个字。 所有分配都组合成一条指令。 大部分数据{text, "hello world!"}是一个文字 。 文字,有时也称为常量,由于它们是模块的一部分并在加载时分配,因此未在函数中分配。

如果堆上没有足够的空间来满足test_heap指令对内存的请求,则将启动垃圾回收。 它可能立即在test_heap指令中发生,或者可以延迟到以后,具体取决于进程所处的状态。如果延迟了垃圾回收,则所需的任何内存都将分配在堆碎片中。 堆碎片是属于年轻堆的一部分的额外内存块,但未在通常驻留术语的连续区域中分配。 有关更多详细信息,请参见年轻堆 。

收藏者

Erlang有一个复制的半空间垃圾收集器。 这意味着进行垃圾回收时,术语会从一个不同的区域(称为from space)复制到一个新的干净区域(称为to space) 。 收集器通过扫描根集 (堆栈,寄存器等)开始。

它遵循从根集合到堆的所有指针,并将每个术语逐字复制到to空间

复制标题词后, 破坏性地放置一个移动标记 ,指向to空间中的术语。 指向已经移动的术语的任何其他术语都将看到此移动标记并代替复制引用指针。 例如,如果具有以下Erlang代码:

foo(Arg) -> T = {test, Arg}, {wrapper, T, T, T}.

堆上仅存在T的一个副本,并且在垃圾回收期间,只有第一次遇到T时才会将其复制。

复制了根集引用的所有术语后,收集器将扫描到空格并复制这些术语所引用的所有术语。 扫描时,收集器将遍历to空间上的每个术语,并且仍然引用from空间的任何术语都将复制到to space上 。 一些术语包含非术语数据(例如,堆上二进制文件的有效负载)。 当收集器遇到这些值时,将直接跳过这些值。

我们可以到达的每个术语对象都被复制到to空间并存储在扫描停止行的顶部,然后将扫描停止移动到最后一个对象的末尾。

扫描停止标记赶上 扫描开始标记时,便完成了垃圾回收。 在这一点上,我们可以从空间中 释放整个堆,从而回收整个年轻堆。

世代垃圾收集

除了上述收集算法之外,Erlang垃圾收集器还提供了分代垃圾收集。 另外一个称为旧堆的堆用于存储长期数据。 原始堆称为年轻堆,有时也称为分配堆。

考虑到这一点,我们可以再次查看Erlang的垃圾回收。 在此期间,应该被复制到年轻的空间复制阶段,任何事情,而不是被复制到旧空间 ,如果它是高水印下方 。

高水位标记放置在先前的垃圾回收( 概述中已描述)结束的位置,并且我们引入了一个称为旧堆的新区域。 在进行正常的垃圾收集过程时,位于高水位线以下的任何术语都会复制到旧的空间而不是年轻的空间

在下一个垃圾回收中,所有指向旧堆的指针都将被忽略并且不会被扫描。 这样,垃圾收集器不必扫描长期存在的条件。

分代垃圾回收旨在以牺牲内存为代价来提高性能。 之所以能够实现这一目标,是因为在大多数垃圾回收中只考虑了较小的年轻堆。

世代假设预测大多数术语倾向于年轻而死,对于像Erlang这样的不可变语言,年轻术语的死亡甚至比其他语言更快。 因此,对于大多数使用模式而言,新堆中的数据在分配后很快就会消失。 这很好,因为它限制了复制到旧堆的数据量,并且还因为使用的垃圾回收算法与堆上的活动数据量成正比。

这里要注意的一个关键问题是,新堆上的任何术语都可以引用旧堆上的术语,但是旧堆上的任何术语都不能引用新堆上的术语。 这是由于复制算法的性质。 旧堆术语所引用的任何内容都不会包含在引用树,根集及其跟随者中,因此不会被复制。 如果是这样,数据将丢失,火和硫磺将覆盖整个地球。 幸运的是,这对于Erlang来说是自然而然的,因为这些术语是不可变的,因此在旧堆上不能修改任何指向年轻堆的指针。

为了从旧堆中回收数据,在收集期间会同时包含新堆和旧堆,并将它们复制到space的公共空间 。 然后,重新分配旧堆和旧堆的from空间 ,并且过程将从头开始。 这种类型的垃圾收集称为完全清除,当高水位标记下的区域大小大于旧堆的可用区域的大小时触发。 也可以通过手动调用erlang:garbage_collect()或遇到spawn_opt(fun(),[{fullsweep_after,N}])设置的年轻垃圾收集限制来触发,其中N是年轻垃圾的数量在强制对旧堆和旧堆进行垃圾回收之前要执行的回收。

年轻的堆

如概述中所述,新堆或分配堆由堆栈和堆组成。 但是,它也包括附加到堆的所有堆片段。 所有堆碎片都被认为高于高水位线并且是年轻一代的一部分。 堆片段包含的术语要么不适合堆,要么由另一个进程创建,然后附加到堆。 例如,如果bif binary_to_term在不进行垃圾回收的情况下创建了一个不适合当前堆的术语,它将为该术语创建一个堆碎片,然后调度垃圾回收以备后用。 同样,如果将消息发送到进程,则有效负载可能会放在堆碎片中,并且当在接收子句中将消息匹配时,该碎片会添加到年轻堆中。

此过程与Erlang / OTP 19.0之前的工作方式不同。 在19.0之前,仅将年轻堆和堆栈所在的连续内存块视为年轻堆的一部分。 堆碎片和消息会立即复制到年轻的堆中,然后再由Erlang程序检查它们。 19.0中引入的行为在许多方面都具有优越性-最重要的是,它减少了必需的复制操作数量和垃圾回收的根集。

调整堆大小

如概述中所述,堆的大小会增加以容纳更多数据。 堆增长分为两个阶段,首先使用233个单词开始的Fibonacci序列变体 。 然后,堆大约以1兆字为单位以20%的增量增长 。

幼堆增长有两种情况:

  1. 如果堆+消息和堆碎片的总大小超过当前堆大小。
  2. 如果经过全扫,则活动对象的总数大于75%。

有两种情况会收缩年轻堆:

  1. 如果在进行年轻收集后,活动对象的总数少于堆的25%,并且年轻堆为“大”
  2. 如果经过全面扫描,则活动对象的总数少于堆的25%。

在堆增长阶段,老堆总是比年轻堆领先一步。

文字

当垃圾收集堆(无论是旧的还是旧的)时,所有文字都保留在原处而不被复制。 为了确定在进行垃圾回收时是否应复制术语,使用了以下伪代码:

if (erts_is_literal(ptr) || (on_old_heap(ptr) && !fullsweep)) { /* literal or non fullsweep - do not copy */ } else { copy(ptr); }

erts_is_literal检查在不同的体系结构和操作系统上的工作方式有所不同。

在允许映射未保留的虚拟内存区域(Windows以外的大多数操作系统)的64位系统上,将映射大小为1 GB(默认)的区域,然后将所有文字放在该区域中。 然后,需要做的就是确定两个对象是否为文字,这是两个快速指针检查 。 该系统依赖于以下事实:尚未被触摸的内存页面不会占用任何实际空间。 因此,即使映射了1 GB虚拟内存,也只会在ram中分配字面量实际需要的内存。 文字区域的大小可以通过+ MIscs erts_alloc选项进行配置。

在32位系统上,没有足够的虚拟存储空间来为文字分配1 GB的空间,因此,按需创建了256 KB的小字面区域,然后使用整个32位存储空间的卡标记位阵列来确定术语是否为文字。 由于总存储空间只有32位,因此卡标记位数组只有256个字大。 在64位系统上,相同的位数组必须大1兆个字,因此该技术仅在32位系统上可行。 在数组中进行查找要比仅在64位系统中可以完成的指针检查要昂贵得多,但并非完全如此。

在erts_alloc无法执行未保留的虚拟内存映射的64位窗口上,使用Erlang术语对象中的特殊标记来确定某些内容是否为文字 。 这是非常便宜的,但是该标记仅在64位计算机上可用,并且将来有可能对此标记进行很多其他不错的优化(例如,例如更紧凑的列表实现),因此不会在不需要它的操作系统上使用。

此行为与Erlang / OTP 19.0之前的工作方式不同。 在19.0之前,通过检查指针是否指向旧堆块或旧堆块来进行文字检查。 如果不是,则将其视为文字。 这导致相当大的开销和奇怪的内存使用情况,因此在19.0中已将其删除。

二进制堆

对于大于64个字节的二进制项(从现在起称为堆外二进制文件),二进制堆充当较大的对象空间。 二进制堆被引用计数,并且指向堆外二进制文件的指针存储在进程堆上。 为了跟踪何时减少堆外二进制文件的引用计数器,通过堆编织了一个包含乐趣和外部因素以及堆外二进制文件的链表(MSO-标记和清除对象列表)。 垃圾收集完成后, MSO列表将被清除 ,并且没有在标头单词中写入移动标记的任何堆外二进制文件的引用将减少,并且有可能被释放 。

MSO列表中的所有项目都按它们添加到进程堆的时间排序,因此,在进行次要垃圾回收时,MSO清除程序只需清除,直到遇到旧堆上的堆外二进制文件为止。

虚拟二进制堆

每个进程都有一个与之关联的虚拟二进制堆,该虚拟二进制堆具有该进程所引用的所有当前堆外二进制文件的大小。 虚拟二进制堆也有一个限制,并且会根据进程使用堆外二进制文件的方式来增长和缩小。 二进制堆和术语堆使用相同的增长和收缩机制,因此首先是斐波那契数列,然后增长20%。

存在虚拟二进制堆是为了在可能存在大量可回收的堆外二进制数据时更早触发垃圾回收。 这种方法并不能解决二进制存储器没有尽快发布的所有问题,但是确实可以解决很多问题。

留言内容

消息可以在不同时间成为进程堆的一部分。 这取决于流程的配置方式。 我们可以使用process_flag(message_queue_data, off_heap | on_heap)配置每个进程的行为,也可以在启动时使用选项+hmqd为所有进程设置默认值。

这些不同的配置会做什么,我们应该何时使用它们? 让我们从一个Erlang进程向另一个进程发送消息时发生的情况开始。 发送过程需要做两件事:

接收方进程的进程标志message_queue_data控制步骤2中发送方进程的消息分配策略,以及垃圾收集器如何处理消息数据。

上面的过程与19.0之前的工作方式不同。 在19.0之前没有配置选项,其行为始终与19.0中的on_heap选项非常相似。

消息分配策略

如果设置为on_heap ,则发送过程将首先尝试直接在接收过程的年轻堆块上为消息分配空间。 这并不总是可能的,因为它需要获取接收过程的主锁 。 当进程执行时,主锁也被保持。 因此,在高度协作的系统中可能发生锁定冲突。 如果发送过程无法获取主锁,则会为消息创建一个堆片段,并将消息有效负载复制到该片段上。 使用off_heap选项,发件人进程始终为发送到该进程的消息创建堆碎片。

试图弄清楚您要使用哪种策略时,会有许多不同的权衡取舍。

使用off_heap似乎是获得更具伸缩性的系统的好方法,因为您对主锁的争用很少,但是,分配堆片段比在接收进程的堆上分配要昂贵。 因此,如果不太可能发生争用,尝试直接在接收进程的堆上分配消息会更有效率。

使用on_heap将强制所有消息成为年轻堆的一部分,这将增加垃圾收集器必须移动的数据量。 因此,如果在处理大量消息时触发了垃圾回收,它们将被复制到年轻堆中。 反过来,这将导致消息将Swift提升到旧堆,从而增加其大小。 这可能是好事,也可能是坏事,具体取决于该过程执行的操作。 大的旧堆意味着年轻的堆也将更大,这反过来意味着在处理消息队列时将触发较少的垃圾回收。 这将临时增加进程的吞吐量,但需要更多的内存使用量。 但是,如果在使用完所有消息之后,该过程将进入一种状态,在该状态下,接收到的消息要少得多。 然后可能要等很长时间才能进行下一个全扫描垃圾收集,并且旧堆上的消息将一直存在,直到发生这种情况为止。 因此,尽管on_heap可能比其他模式更快,但它会在更长的时间内使用更多的内存。 此模式是传统模式,几乎是在Erlang / OTP 19.0之前处理消息队列的方式。

这些策略中哪一个最好,在很大程度上取决于该流程在做什么以及与其他流程的交互方式。 因此,与往常一样,对应用程序进行概要分析,并查看其在不同选项下的行为。

[1]:CJ Cheney。 非递归列表压缩算法。 公社 ACM,13(11):677-678,1970年11月。

[2]:D。Ungar。 生成清除:一种无中断的高性能存储回收算法。 SIGSOFT软件。 。 笔记,9(3):157-167,1984年4月。

了解有关我们与 Erlang Elixir 合作的更多信息

最初在 www.erlang-solutions.com上 发布

翻译自: https://hackernoon.com/erlang-garbage-collector-dfc9ad952130

erlang 垃圾回收



推荐阅读
  • 兆芯X86 CPU架构的演进与现状(国产CPU系列)
    本文详细介绍了兆芯X86 CPU架构的发展历程,从公司成立背景到关键技术授权,再到具体芯片架构的演进,全面解析了兆芯在国产CPU领域的贡献与挑战。 ... [详细]
  • 本文回顾了作者初次接触Unicode编码时的经历,并详细探讨了ASCII、ANSI、GB2312、UNICODE以及UTF-8和UTF-16编码的区别和应用场景。通过实例分析,帮助读者更好地理解和使用这些编码。 ... [详细]
  • 在 Linux 环境下,多线程编程是实现高效并发处理的重要技术。本文通过具体的实战案例,详细分析了多线程编程的关键技术和常见问题。文章首先介绍了多线程的基本概念和创建方法,然后通过实例代码展示了如何使用 pthreads 库进行线程同步和通信。此外,还探讨了多线程程序中的性能优化技巧和调试方法,为开发者提供了宝贵的实践经验。 ... [详细]
  • 本文介绍了Java编程语言的基础知识,包括其历史背景、主要特性以及如何安装和配置JDK。此外,还详细讲解了如何编写和运行第一个Java程序,并简要介绍了Eclipse集成开发环境的安装和使用。 ... [详细]
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • 在Windows系统中安装TensorFlow GPU版的详细指南与常见问题解决
    在Windows系统中安装TensorFlow GPU版是许多深度学习初学者面临的挑战。本文详细介绍了安装过程中的每一个步骤,并针对常见的问题提供了有效的解决方案。通过本文的指导,读者可以顺利地完成安装并避免常见的陷阱。 ... [详细]
  • 本文对SQL Server系统进行了基本概述,并深入解析了其核心功能。SQL Server不仅提供了强大的数据存储和管理能力,还支持复杂的查询操作和事务处理。通过MyEclipse、SQL Server和Tomcat的集成开发环境,可以高效地构建银行转账系统。在实现过程中,需要确保表单参数与后台代码中的属性值一致,同时在Servlet中处理用户登录验证,以确保系统的安全性和可靠性。 ... [详细]
  • Hadoop平台警告解决:无法加载本机Hadoop库的全面应对方案
    本文探讨了在Hadoop平台上遇到“无法加载本机Hadoop库”警告的多种解决方案。首先,通过修改日志配置文件来忽略该警告,这一方法被证明是有效的。其次,尝试指定本地库的路径,但未能解决问题。接着,尝试不使用Hadoop本地库,同样没有效果。然后,通过替换现有的Hadoop本地库,成功解决了问题。最后,根据Hadoop的源代码自行编译本地库,也达到了预期的效果。以上方法适用于macOS系统。 ... [详细]
  • 为了在Hadoop 2.7.2中实现对Snappy压缩和解压功能的原生支持,本文详细介绍了如何重新编译Hadoop源代码,并优化其Native编译过程。通过这一优化,可以显著提升数据处理的效率和性能。此外,还探讨了编译过程中可能遇到的问题及其解决方案,为用户提供了一套完整的操作指南。 ... [详细]
  • MATLAB字典学习工具箱SPAMS:稀疏与字典学习的详细介绍、配置及应用实例
    SPAMS(Sparse Modeling Software)是一个强大的开源优化工具箱,专为解决多种稀疏估计问题而设计。该工具箱基于MATLAB,提供了丰富的算法和函数,适用于字典学习、信号处理和机器学习等领域。本文将详细介绍SPAMS的配置方法、核心功能及其在实际应用中的典型案例,帮助用户更好地理解和使用这一工具箱。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 本文深入探讨了Java多线程环境下的同步机制及其应用,重点介绍了`synchronized`关键字的使用方法和原理。`synchronized`关键字主要用于确保多个线程在访问共享资源时的互斥性和原子性。通过具体示例,如在一个类中使用`synchronized`修饰方法,展示了如何实现线程安全的代码块。此外,文章还讨论了`ReentrantLock`等其他同步工具的优缺点,并提供了实际应用场景中的最佳实践。 ... [详细]
  • 如何精通编程语言:全面指南与实用技巧
    如何精通编程语言:全面指南与实用技巧 ... [详细]
author-avatar
木木三623
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有