1.逃逸分析:代码优化
-
使用逃逸分析,编译器可以对代码做以下的优化:
1.栈上分配。将堆分配转化为栈分配,如果一个对象在子程序中被分配,要使指向该对象的指针永远都不会逃逸,对象可能是栈分配的候选,而不是堆分配。
2.同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。
3.分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。
2.代码优化-栈上分配
- JTI编译器在编译期间根据逃逸分析的结果,发现如果一个对象并没有逃逸出方法的话,就可能被优化成栈上分配。分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。这样就不需要进行GC操作。
- 常见的栈上分配的场景
3.代码优化-同步省略(消除)
-
线程同步的代价是相当高的,同步的后果是降低并发性和性能。
-
在动态编译同步块的时候,JIT编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被发布到其他线程。如果没有,那么JIT编译器在编译这个同步块的时候就会取消对这部分代码的同步。这样就能大大提高并发性和性能。这个取消同步的过程就叫同步省略,也叫锁消除。
-
如下,F1为优化后的效果;因为hollis对象的生命周期只在F1()方法中,不会被其他线程所访问到,所以在JIT编译阶段就会被优化掉。
![技术分享图片](http://image1.bubuko.com/info/202105/20210503185838867069.png)
4.代码优化-标量替换
- 标量(Scalar)是指一个无法再分解成更小的数据的数据。Java中的原始数据类型就是标量。相对的,那么些还可以分解的数据叫做聚合量(Aggregate),Java中的对象就是聚合量,因为他可以分解成其他的聚合量和标量。
- 在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过JIT优化,就会把这个对象拆解成若干个其中包含的若干个成员变量来代替。这个过程就叫标量替换。
我们可以看看下面标量替换前和标量替换后的区别:
![技术分享图片](http://image1.bubuko.com/info/202105/20210503185839226444.png)
![技术分享图片](http://image1.bubuko.com/info/202105/20210503185839570194.png)
- 可以发现Ponit这个聚合量经过逃逸分析后,发现它并没有逃逸,就被替换成两个聚合量了。那么标量替换有什么好处呢?就是可以大大减少堆内存的占用。因为一旦不需要创建对象了,那么就不再需要分配堆内存了。可见:标量替换为栈上分配提供了很好的基础。
- 标量替换参数设置:
参数-XX:+EliminateAllocations:开启了标量替换(默认打开),允许将对象打散分配在栈上。
逃逸分析总结:逃逸分析并不成熟
- 关于逃逸分析的理论早在1999年就有了,但直到JDK6才实现,而且这项技术目前为止也不怎么成熟。
- 其根本原因就是无法保证逃逸分析的性能消耗一定能高于他的消耗。虽然经过逃逸分析可以做标量替换,栈上分配,和锁消除。但是逃逸分析自身也是需要进行一系列复杂的分析的,这其实也是一个相对耗时的过程。
- 一个极端的例子,就经过逃逸分析后,发现没有一个对象是不逃逸的。那么这个逃逸分析的过程就浪费了,也会消耗资源。
- 虽然这项技术现在不成熟,但是它也是即时编译器优化技术中一个十分重要的手段。
虚拟机-堆的总结
- 年轻代是对象的诞生,成长,消亡的区域,一个对象在这里产生,应用,最后被垃圾回收器收集,结束生命。
- 老年代放置长生命周期的对象,通常都是从Survivor区域筛选拷贝过来的Java对象。当然,也有一些特殊情况,我们知道普通的对象会被分配在TLAB上;如果对象较大,JVM会视图直接分配在Eden其他位置上;如果对象太大,完全无法在新生代找到足够长的连续空闲空间,JVM就会直接分配到老年代。
- 当GC只发生在年轻代中,回收年轻代对象的行为被称为Minor GC。当GC发生在老年代时则被称为Major GC或者Full GC。一般的,MinorGC的发生频率要比Major GC高很多,也就是老年代的GC操作频率要比新生代中低很多。
虚拟机-堆(三)
原文:https://www.cnblogs.com/yizhipangmayuan/p/14727556.html