加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

意外的Scala集合内存行为

发布时间:2020-12-16 18:11:56 所属栏目:安全 来源:网络整理
导读:以下 Scala代码(在2.9.2上): var a = ( 0 until 100000 ).toStreamfor ( i - 0 until 100000 ){ val memTot = Runtime.getRuntime().totalMemory().toDouble / ( 1024.0 * 1024.0 ) println( i,a.size,memTot ) a = a.map(identity)} 在循环的每次迭代中使
以下 Scala代码(在2.9.2上):

var a = ( 0 until 100000 ).toStream
for ( i <- 0 until 100000 )
{
    val memTot = Runtime.getRuntime().totalMemory().toDouble / ( 1024.0 * 1024.0 )
    println( i,a.size,memTot )

    a = a.map(identity)
}

在循环的每次迭代中使用不断增加的内存量.如果a被定义为(0到100000).toList,则内存使用量是稳定的(给予或采用GC).

我理解流可以懒惰地评估,但一旦生成就保留元素.但似乎在上面的代码中,每个新流(由最后一行代码生成)以某种方式保留对先前流的引用.有人可以解释一下吗?

解决方法

这是发生了什么.流总是被懒惰地评估,但已经计算的元素被“缓存”以供以后使用.懒惰的评估至关重要.看看这段代码:

a = a.flatMap( v => Some( v ) )

虽然看起来好像你正在将一个Stream转换为另一个Stream而丢弃旧的Stream,但事实并非如此.新的Stream仍然保留对旧的Stream的引用.这是因为结果Stream不应急切地计算底层流的所有元素,而是按需执行.以此为例:

io.Source.fromFile("very-large.file").getLines().toStream.
  map(_.trim).
  filter(_.contains("X")).
  map(_.substring(0,10)).
  map(_.toUpperCase)

您可以根据需要链接任意数量的操作,但只读取第一行文件.每个后续操作只包装前一个Stream,保存对子流的引用.当你要求大小或做foreach时,评估开始.

回到你的代码.在第二次迭代中,您创建第三个流,保持对第二个流的引用,该引用依次保留对您最初定义的引用的引用.基本上你有一堆相当大的物体在增长.

但这并不能解释为什么内存泄漏如此之快.关键部分是… println(),或者说是精确的a.size.没有打印(从而评估整个流)流仍然是“未评估”.未评估的流不会缓存任何值,因此它非常小.由于彼此之间的流链不断增长,内存仍会泄漏,但速度要慢得多.

这引出了一个问题:为什么它与toList一起使用它非常简单. List.map()急切地创建新的List.期.之前的一个不再被引用并且符合GC的条件.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读