目录
JVM 逃逸分析
JIT——是即时(Just-In-Time)编译器是Java运行时环境的一个组件,它可提高运行时Java应用程序的性能。JVM中没有什么比编译器更能影响性能,而选择编译器是运行Java应用程序时做出的首要决定之一,无论您是Java开发人员还是最终用户。
随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配,标量替换优化技术会导致一些微妙的变化,之前的一些固定的技术理论,现在显得也不是那么绝对了。
何为逃逸分析
逃逸分析就是一种算法,基于这个算法我们可以有效的减少Java对象在堆中的分配,也就是不在堆中创建过多的对象,而是把这些未逃逸的对象放在了栈内存中,但是这些对象也要满足一些必然的条件。
当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。
以上就是对逃逸现象的基本解释。
通过代码演示
假设我们现在需要拼接字符串并返回一个拼接好的字符串
public StringBuffer append(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb;
}
这段代码中,由于该方法会返回一个StringBuffer对象,所以外部可以调用这个对象,那么它将不会被分配在栈内存上,这就叫做发生了逃逸。我们可以将其修改为以下这个样子:
public String append(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
我们可以看到这段代码和上面不同的是,StringBuffer这个对象,并没有外部的引用或指向,只是在方法内部构建完成后,返回的是String类型的对象,所以StringBuffer就是未发生逃逸的对象,它就会被分配在栈内存,而不是堆内存中,当然这只是举个例子,并不是实际中的应用。
我们需要掌握的是这种思想,当我们编写代码,或者优化代码时,要尽可能的考虑到逃逸问题,因为分配在栈内存中的对象,方法结束后会自动销毁,无需调用GC,可以极大的提高系统的性能。
逃逸分析参数设置
前面两个字段是设置最大堆内存和小堆内存 。我们可以跑一段发生逃逸的代码段,其中sb对象被外部调用,发生逃逸,我们看下它在堆内存上的空间。
public class Test {
public static StringBuffer append(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb;
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
append("hello", "world");
}
long end = System.currentTimeMillis();
System.out.println("花费的时间为: " + (end - start) + " ms");
}
}
我们可以看到伊甸园区占用了24% 幸存区的form space占了 15%。下面我们让StringBuffer这个对象不发生逃逸。
public class Test {
public static String append(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
append("hello", "world");
}
long end = System.currentTimeMillis();
System.out.println("花费的时间为: " + (end - start) + " ms");
}
}
这次堆中的伊甸园只占用了10%,有明显的下降,证明一部分对象被存在了栈内存上
所以我们建议:开发中能在方法内部应用对象的,就尽量控制在内部。