vlambda博客
学习文章列表

scala的lazy关键字字节码分析

先简单说下lazy关键字的作用:

  1. 只能修饰常量

  2. 惰性值在赋值的时候并不触发,只有在要用到的时候才会触发 

 

如上图,debug到第11,a和b都是0,并不会触发a=1赋值,以及sum方法的运行

 

scala的lazy关键字字节码分析

如下图,只有用到的时候才会触发赋值和方法调用



开始分析字节码实现:

代码

object Test1 {
def sum:Int = {
2
}

def main(args: Array[String]): Unit = {
lazy val a =1
lazy val b =sum
print("fasfd")
var c =a
var d =b
print("ok")
}

}

我们先看整个方法:我们多了四个方法

scala的lazy关键字字节码分析

 

然后看下main方法:

scala的lazy关键字字节码分析

很明显:

    lazy val a =1

    lazy val b =sum

的时候,只是 new了两个为0的 IntRef ----这块就不先展开了

 

真正的"赋值"/函数调用是发生在

    var c =a

    var d =b

的时候,这时候看上图的两条绿线,发生了对函数a$1 和b$1的调用

所以这就是所谓的懒加载:

 

我们再看:

    lazy val b =sum对应的方法 b$1

scala的lazy关键字字节码分析scala的lazy关键字字节码分析

b$1有三个局部变量

0:this指针

1:IntRef --就是在main方法开始执行 lazy val b =sum中 并不执行sum方法,而直接new的一个初始化中 IntRef(0)

2.一个VolitaleByteRef对象,值是0--后面再说,会用到

scala的lazy关键字字节码分析

由于lazy无法修饰var变量 ,所以lazy只能修饰val 变量 ,所以 lazy 变量只需要被赋值一次就行了,绿框中的的判断条件,明显是如果其他地方调用赋值后,(这块是其他地方如果赋值了,会putfield改变VolitaleByteRef的elem,这样在 if_icmpne 时会跳到20---具体逻辑和线程安全看)

我们在获取lazy 变量时就直接跳转到20步,直接通过 getfield 来获取 lazy变量的值了,不需要再重新赋值了


 

我们调用b$1其实是为了调用真正发生逻辑的b$Izycompyte$1方法

这个方法如下:

scala的lazy关键字字节码分析

scala的lazy关键字字节码分析

整个方法都被sync锁着 ---就是sync(this) --因为dup复制了this指针

,所以我们惰性赋值是线程安全的,

然后在进入赋值前还是先判断一次惰性值是否被其他地方赋值过

scala的lazy关键字字节码分析

 

如下图,在退出锁前,修改了VolitaleByteRef的elem,让其他引用到惰性变量的地方可以不用再赋值了

scala的lazy关键字字节码分析

 

然后结合异常表

scala的lazy关键字字节码分析

 

scala的lazy关键字字节码分析

如上图我们在4-40步的时候发生任何异常和错误,都会解锁并抛出来

真正的赋值调用在:绿框中

scala的lazy关键字字节码分析

 

a$1和对应的compute方法就不分析了

 

总结:


更清晰的总结:----从别的地方盗的图---不对应本文代码