by 十三
背景:
近日遇到个奇怪的问题:在没有明显循环或递归的代码地方,日志中却抛出了stackoverflow的错误,由于代码入口处是调用了configuration获取yarn的配置,所以刚开始定位思路有点跑偏了,后来就尝试去掉了lazy关键词,发现错误没有了,根据后续的日志打印发现,跟预期有出入,才发现是因为log中使用了错误的变量(看来是写代码太不走心了,走查代码也没走心,😢),至此才发现问题根因。
原因:
我们先来看如下两段代码的区别
片段1:
lazy val upperLimit: Int = {
val increment: Int = 512//最初是conf.getSomeValue方法,方便起见赋初值
val containerMaxMem: Int = 20//最初是conf.getSomeValue方法,方便起见赋初值
val upper = (containerMaxMem - math.ceil(math.max(containerMaxMem * 0.1 * 1024, 384)) / increment * increment / 1024.0).toInt
log.debug(s"upper = $upperLimit")
upper
}
println(upperLimit)
片段2:
val upperLimit: Int = {
val increment: Int = 512
val containerMaxMem: Int = 20
val upper = (containerMaxMem - math.ceil(math.max(containerMaxMem * 0.1 * 1024, 384)) / increment * increment / 1024.0).toInt
log.debug(s"upper = $upperLimit")
upper
}
println(upperLimit)
从代码的角度来看,唯一的区别在于前者多了一个lazy,那么两段代码的输出会有什么差别吗?
我们来看看输出结果:
片段1的输出:
An exception or error caused a run to abort.
java.lang.StackOverflowError
at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691)
at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:579)
at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271)
at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
at java.io.PrintStream.write(PrintStream.java:526)
at java.io.PrintStream.print(PrintStream.java:669)
at java.io.PrintStream.println(PrintStream.java:823)
at scala.Console$.println(Console.scala:148)
at scala.Predef$.println(Predef.scala:315)
片段2的输出:
0
18
片段1中由于加了lazy关键词,所以在log中首次使用的时候,才会去真正计算它的值,在没有得到返回值之前,log中又开始计算值,所以不断地压栈,最终导致栈溢出的错误。
而片段2中,去掉了lazy关键词,log中首次使用的时候,就会使用Int类型的初始值,也就是0.
最后再看一看
scala> lazy val a = 1
a: Int = <lazy>
scala> a
res1: Int = 1
scala> val a = 1
a: Int = 1
lazy虽好,使用还得谨慎。