前言
最近在学习多线程这块内容的时候,了解到了JVM的逃逸分析,现在就简单地记录下。
逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。
逃逸分析的优化方向
- 堆分配优化为栈分配
- 同步省略
- 标量替换或分离对象
怎么判断是否逃逸
是否逃逸就是判断对象的作用域:当对象在方法中被定义后,如果这个对象被外界所持有并使用。
public static UserInfo setUserInfo(String name, int age) {
UserInfo userInfo = new UserInfo();
userInfo.setName(name);
userInfo.setAge(age);
return userInfo;
}
UserInfo userInfo是方法中的内部变量,但是以上代码却通过方法参数进行设置姓名与年龄并将该UserInfo对象返回,外界的类XXClass可以调用该方法,并且可以修改该UserInfo类,虽然他是个局部变量,但是逃逸到了方法外部,而且还能够被外部线程访问,因此称之为方法逃逸或线程逃逸。
public static UserInfo setUserInfo(String name, int age) {
UserInfo userInfo = new UserInfo();
userInfo.setName(name);
userInfo.setAge(age);
// 返回toString
return userInfo.toString();
}
以上的代码是UserInfo局部变量并未逃逸出方法的写法。
栈分配
首先,提一点基础知识,Java程序在运行期间,在内存中划分5个区域进行存储数据,分别是:寄存器、本地方法区、方法区、堆、栈。和本次博客相关的堆与栈,其中堆用来存放new关键字实例化的对象以及数组,栈用来存放基本数据类型以及局部变量。栈用于保存对象首地址值,并将该变量指向对应的对象存储在堆中的位置。如果发现该对象并没有逃逸出方法,或者线程,那么有可能在栈上进行分配,无需再堆上分配,也就不需要GC了。
同步省略
public String getObj(){
Object obj = new Object();
synchronized (obj) {
String result = createString();
return result;
}
}
以上代码我们在学习多线程的时候,知道这么写是无效锁,因为每次锁锁住的都是新的对象,一个合理的受保护的资源与锁之间的关系应该是N:1,结合逃逸分析,其实这段代码最终会被JIT优化掉, 因为Object对象没有逃逸出方法,对锁对象进行分析只能够被一个线程访问,JIT会取消对这部分代码进行同步,最终优化后的代码:
public String getObj(){
Object obj = new Object();
String result = createString();
return result;
}
标量替换
所谓标量即不可再进一步分解的量,例如Java基本数据类型就是标量,相反可以被进一步分解的就叫做聚合量,例如Java中创建的对象就是可以进一步分解的聚合量。
同样如果分析一个对象没有逃逸,并且该对象可以进一步进行分解,那么就可以进行标量替换。如下:
private void setLocation(double longitude, double latitude){
Location location = new Location(longitude, latitude)
}
class Location{
double longitude;
double latitude;
Location(){
// 省略构造
}
}
Location对象没有逃逸出方法,而且Location对象是可以拆解成标量,所以JIT不会直接创建Location对象。因此优化后代码如下:
private void setLocation(double longitude, double latitude){
double longitude = longitude;
double latitude = latitude;
}
// 被优化掉
class Location{
double longitude;
double latitude;
Location(){
// 省略构造
}
}
以上就是所谓的逃逸分析。