Scala 中闭包的内存管理是如何工作的?

Posted

技术标签:

【中文标题】Scala 中闭包的内存管理是如何工作的?【英文标题】:How does the memory management of closures in Scala work? 【发布时间】:2013-06-12 14:57:42 【问题描述】:

Scala 允许像这样关闭

def newCounter = 
  var a=0
  () => a+=1;a

它定义了一个函数,每次调用都返回一个新的独立计数器函数,从1开始:

scala> val counter1 = newCounter
counter1: () => Int = <function0>

scala> counter1()
res0: Int = 1

scala> counter1()
res1: Int = 2

scala> val counter2 = newCounter
counter2: () => Int = <function0>

scala> counter2()
res2: Int = 1

scala> counter1()
res3: Int = 3

这令人印象深刻,因为通常a 将代表 newCounter 堆栈帧上的内存地址。我刚刚阅读了“Scala 编程”的闭幕章节,关于这个问题只有以下内容要说(第 155 页):

在这种情况下,Scala 编译器会重新排列事物,以便捕获的参数在堆中而不是堆栈中存在,因此可以比创建它的方法调用更有效。这个重排都是自动处理的,所以你不用担心。

谁能详细说明这在字节码级别是如何工作的?访问是否类似于具有所有相关同步和性能影响的类的成员变量?

【问题讨论】:

这被称为“funarg 问题”,我猜 wiki 可能有一些关于理论背景的指针:en.wikipedia.org/wiki/Funarg_problem。一般的解决方案似乎是“将激活记录或其中的一部分放在堆上”。 (谷歌也可以找到一些关于这个的演讲幻灯片/笔记或论文。) 注意 SIP 21 “孢子”(什么名字!)docs.scala-lang.org/sips/pending/spores.html 相关问题:***.com/questions/12831024/… 【参考方案1】:

您可以使用scalac -Xprint:lambdalift &lt;scala-file-name&gt; 对此进行调查。

你的代码实际上是这样的:

def newCounter = 
  val a: runtime.IntRef = new runtime.IntRef(0);
  new Function0 
    private[this] val a$1 = a
    def apply() = 
      a$1.elem = a$1.elem + 1
      a$1.elem
    
  

lambda 使用的任何var 都有一个包装器。其他vars(不用于闭包)是常见的语言环境变量。

此包装器的链接存储为函数实例中的字段。

-Xprint:lambdalift 中的lambdalift 是compiler phase。您可以使用-Xshow-phases 获取所有阶段。您可以使用阶段编号而不是名称,这在您不确定需要哪个阶段时很有用。

【讨论】:

命令行中的“20”是什么意思? 顺便说一句,将其与闭包中未使用的另一个外部变量进行对比会很有趣。 @pedrofurla:我已经更新了我的答案。 20 太过分了,lambdalift (15) 就够了。 @pedrofurla: What does the '20' mean in the cmd-line?...suggest improvement 的有趣方式。 我不是在建议改进,我是在问。我不太关心 SO 规则...

以上是关于Scala 中闭包的内存管理是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

JS高级——内存管理和闭包

JS高级——内存管理和闭包

JavaScript内存管理闭包和内存泄漏

JavaScript高级内存管理与闭包:垃圾回收GC闭包定义访问和执行过程内存泄漏

JavaScript内存泄露,闭包内存泄露如何解决

Flink内存管理源码解读之基础数据结构