下面这两份代码哪个好,(从jvm层面考虑的话)好在哪?
public StringBuffer createString1(String ... values){
StringBuffer stringBuffer = new StringBuffer();
for (String string : values) {
stringBuffer.append(string+" ");
}
return stringBuffer;
}
public String createString2(String ... values){
StringBuffer stringBuffer = new StringBuffer();
for (String string : values) {
stringBuffer.append(string+" ");
}
return stringBuffer.toString();
}
看完我下面的文章之后就知道了。
逃逸分析是编译语言中的一种优化分析,而不是一种优化的手段。通过对象的作用范围的分析,为其他优化手段提供分析数据从而进行优化。
是不是听得有点懵?用“土鳖”一点的大白话来说就是,在方法内你new出来的对象只能在方法内随便折腾(业务逻辑处理),但是你不能逃逸出方法的这个一亩三分地而为外部方法所利用。再精简一点可以理解成:对象在我方法内活着,出方法则消亡。
public class EscapeAnalysis {
public static Object object;
//全局变量赋值逃逸
public void globalVariableEscape(){
object =new Object();
}
//方法返回值逃逸:方法内new出来的对象逃离案发现场(即方法范围)而有机会被其他方法获取该对象
public Object methodEscape(){
return new Object();
}
//实例引用发生逃逸
public void instancePassEscape(){
this.speak(this);
}
public void speak(EscapeAnalysis escapeAnalysis){
System.out.println("Escape Hello");
}
}
-XX:+DoEscapeAnalysis 开启逃逸分析
-XX:-DoEscapeAnalysis 关闭逃逸分析
通过jmap -histo [pid]查看java堆上的对象分布情况:
标量(scalar replacement):就是不能再被分解的量。(例如八大基本类型 byte , short , int ,long ,char , float ,double , boolean)另外指向对象的引用也是标量。
聚合量(aggregate)就是还能继续被分解的量,例如对象能被分解成多个标量。
如果把一个Java对象拆散,将其成员变量恢复为分散的变量,这就叫做标量替换。拆散后的变量便可以被单独分析与优化,可以各自分别在活动记录(栈帧或寄存器)上分配空间;原本的对象就无需整体分配空间了。
通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM不会创建该对象,而会将该对象成员变量分解若干个被这个方法使用的成员变量所代替。这些代替的成员变量在栈帧或寄存器上分配空间。
-XX:+EliminateAllocations 可以开启标量替换
-XX:+PrintEliminateAllocations 查看标量替换情况(Server VM 非Product版本支持)
震惊?!不是所有的对象都在堆上分配内存空间!
只有发生了逃逸的对象,才会在堆上分配内存,JIT即使编译技术优化中加入了逃逸分析,就是专门分析对象是否发生逃逸的,如果发现对象的作用域仅限于本方法内,也就是外部没有任何引用,那么就让对象在栈帧执行到该方法时在栈上分配内存。随着方法执行完毕,返回一个标量基本数据类型后,栈帧弹出栈,所有分配的内存回收,当然也包括那个对象的内存,所以这效率肯定比GC不知道啥时候才来打扫卫生要高得多。
通过-XX:+EliminateLocks可以开启同步消除,进行测试执行的效率
同步消除是java虚拟机提供的一种优化技术。这里消除的其实是对象的同步锁,堆被线程共享,那么堆上面的对象也会被线程共享,大家一起读写,会出现同步的并发问题,所以对象也加了synchronized同步锁。但是栈是线程独享的,如果进行栈上分配,那么就根本不需要同步锁,提高了执行效率。这就是同步消除。
经过上面的总结可以发现:
(1)消除了同步锁,提高了运行速度。
(2)减轻了垃圾收集子系统GC的负担,标量替换使得减少了对象回收的一串漫长流程。
(3)提高了内存利用率,栈上分配可以更好管理内存使用和回收。
震惊?!这么多好处!JDK中逃逸分析居然还不成熟?!
这个概念书上说1999年就提出来了,是非常好的一个想法,但是直到现在也还不成熟,因为代码编写的原因,没有严格限制返回类型,图方便我也会整个对象返回。又因为整个逃逸分析流程较长(整套逃逸分析,标量替换,栈上分配,同步消除,整套大宝剑做下来也很耗费性能资源)。结果就造成整套分析下来全部对象还要乖乖在堆上分配内存。速度更慢了。
但是这是即时编译技术JIT的非常好的一个模式,相信将来会有更大的发展空间。
本文最后给大家一点个人的小建议,其实所有的知识都是串联的。希望大家通过本文能够将JVM逃逸分析,synchronized锁的底层实现,JVM垃圾回收等知识融会贯通形成自己的知识体系,同时反思自己平常写的代码中是否有注意到进行标量替换来提升代码执行效率。