堆是分配对象的唯一选择吗?
不是。
随着Java
的发展和逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术会使原有的绝对堆上分配发生一些微妙的变化。
逃逸分析
如果一个对象并没有逃逸出方法的话,那么就可能被优化为栈上分配。
- 如果有个对象在方法中被定义,且只在方法内部使用,则认为它没有发生逃逸。
- 当一个对象在方法中被定义后,它被外部方法引用,则认为发生了逃逸。
如何将一个会逃逸的对象变成非逃逸对象?
//逃逸
public StringBuilder test() {
StringBuilder sb = new StringBuild();
return sb;
}
//非逃逸
public String test() {
StringBuilder sb = new StringBuild();
return sb.toString();
}
⑩① 使用逃逸分析进行优化
1) 栈上分配
public class Main {
public static void main(String[] args) {
long start = System.currentTimeMillis();
/* 执行一千万次 */
for (int i = 0; i < 10000000; i++) {
CreateObj();
}
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "ms");
}
public static void CreateObj() {
/* 未发生逃逸 */
User user = new User();
}
}
class User { }
首先我们关闭逃逸分析。
得到运行耗时:46ms
。
开启逃逸分析。
得到运行耗时:2ms
。
修改代码:
public class Main {
public static void main(String[] args) {
long start = System.currentTimeMillis();
User user = null;
/* 执行一千万次 */
for (int i = 0; i < 10000000; i++) {
user = CreateObj();
}
/* Obj 可能被其他线程继续使用 */
userObj(user);
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "ms");
}
public static User CreateObj() {
/* 发生逃逸 */
User user = new User();
return user;
}
public static void userObj(User user) { }
}
class User {
public int age = 5;
}
使代码发生逃逸,且开启逃逸分析。
得到运行时间:43ms
。
2) 同步省略
如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。
3) 分离对象或标量替换
有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU
寄存器中。
标量指一个无法再分解成更小得数据的数据,例如基本数据类型就是标量。
聚合量指的是那些还可以分解为 标量 或 聚合量 的数据。
**标量替换:**在JIT
阶段,如果经过逃逸分析,发现一个对象并没有发生逃逸,则会将这个对象拆解为多个标量的状态。
例如:
public class Main {
public static void main(String[] args) {
test();
}
public static void test() {
Point point = new Point();
}
}
class Point {
int x;
int y;
}
此时,经过标量替换,栈中Point
实例会被替换为。
public class Main {
public static void main(String[] args) {
test();
}
//此处被替换
public static void test() {
int x;
int y;
}
}
class Point {
int x;
int y;
}