JVM中逃逸分析和标量替换和栈上分配和同步消除
最近也是疫情严重,呆在家里把JVM虚拟机内容慢慢又过一遍,把一些内容再做个总结加深下自己印象。
逃逸分析:
逃逸分析其实就是分析一个方法中创建出来的‘对象’的作用域是否限制于本方法内。单例模式就是一个典型的逃逸分析。
翻译:假设你正在跑步机跑步(这是一个running方法),这时候饿了(有需要),让妈妈给你拿一碟小番茄(在另外一个方法里 new 小番茄 = 小番茄() );你把一碟都拿走了,这就是逃逸分析。意思就是妈妈创建出来的小番茄对象必须整个对象给你,所以new出来的那个对象就逃逸出了妈妈方法的控制。
上代码:
可以看出main类里面有个getDemo方法,里面创建并且返回了一个Main类型的demo对象,然后main方法接受到了main类型的demo对象,这就是逃逸了。
那怎么样才算不发生逃逸呢?先来看两个概念。
标量和聚合量:
标量就是不能再被分解的量。(例如八大基本类型 byte , short , int ,long ,char , float ,double , boolean ).
聚合量就是还能继续被分解的量,例如对象能被分解成一个个基本类型数据。
还是上面那个例子,Main类型的demo对象是聚合量,int name 和 int age 就是标量;
那么,怎么样才算是不发生逃逸呢?
标量替换:
有时候我们返回一个对象,其实也只是想用对象里面的某个属性而已,所以这时候我们可以写成:
这个就是标量替换,方法不直接返回对象类型,而是返回一个基本类型,也就是标量,那么new出来的对象的作用域就还是在getDemo方法里面,不会跑到main方法去。
震惊?!不是所有的对象都在堆上分配内存空间
栈上分配:
只有发生了逃逸的对象,才会在堆上分配内存,JIT即使编译技术优化中加入了逃逸分析,就是专门分析对象是否发生逃逸的,如果发现对象的作用域仅限于本方法内,也就是外部没有任何引用,那么就让对象在栈帧执行到该方法时在栈上分配内存。随着方法执行完毕,返回一个标量基本数据类型后,栈帧弹出栈,所有分配的内存回收,当然也包括那个对象的内存,所以这效率肯定比GC不知道啥时候才来打扫卫生要高得多。
同步消除:
这里消除的其实是对象的同步锁,堆被线程共享,那么堆上面的对象也会被线程共享,大家一起读写,会出现同步的并发问题,在另外一篇的博客说的买票就是这么回事,所以对象也加了synchronized同步锁。但是栈是线程独享的,如果进行栈上分配,那么就根本不需要同步锁,提高了执行效率。这就是同步消除。
做逃逸分析有什么好处:
经过上面的总结可以发现:
(1)消除了同步锁,提高了运行速度。
(2)减轻了垃圾收集子系统GC的负担,标量替换使得减少了对象回收的一串漫长流程。
(3)提高了内存利用率,栈上分配可以更好管理内存使用和回收。
震惊?!这么多好处!JDK中逃逸分析居然还不成熟?!
这个概念书上说1999年就提出来了,是非常好的一个想法,但是直到现在也还不成熟,因为代码编写的原因,没有严格限制返回类型,图方便我也会整个对象返回。又因为整个逃逸分析流程较长(整套逃逸分析,标量替换,栈上分配,同步消除,整套大宝剑做下来也很耗费性能资源)。结果就造成整套分析下来全部对象还要乖乖在堆上分配内存。速度更慢了。
但是这是即使编译技术JIT的非常好的一个模式,相信将来会有更大的发展空间