JIT Compiler(Just-in-timeCompiler) 即时编译。
最早的Java建置方案是由一套转译程式(interpreter),将每个Java指令都转译成对等的微处理器指令,并根据转译后的指令先后次序依序执行,由于一个Java指令可能被转译成十几或数十几个对等的微处理器指令,这种模式执行的速度相当缓慢。
详细解析见百度百科 JIT编译器
Escape Analysis 内存逃逸分析是一种代码分析手段,通过动态分析创建对象的使用范围。
如果一个方法创建的对象,除了方法内使用到之外,还被方法外使用到,那么在方法执行结束后,由于该对象依然被引用到,那么GC就可能无法立即回收,此时该对象就出现了逃逸。
内存逃逸分为方法逃逸和线程逃逸
public class EscapeAnalysis {
public static Object obj1;
public Object obj2;
public void globalVariableEscape() {
obj1 = new Object(); // 静态变量,外部线程可见,会发生逃逸
}
public void instanceObjectEscape() {
obj2 = new Object(); // 赋值给堆中实例字段,外部线程可见,会发生逃逸
}
public Object returnObjectEscape() {
return new Object(); // 返回实例,外部线程可见,会发生逃逸
}
public void noEscape() {
Object noEscape = new Object(); // 仅创建线程可见,对象无逃逸
}
}
筛选出没有发生逃逸的对象,为其优化手段,例如栈上分配,标量替换,同步消除等提供依据。
只有server模式下才能启用逃逸分析

JVM相关参数
同步锁时非常消耗性能的,所以编译器确定一个对象没有发生逃逸时,它会移除该对象的同步锁。
JDK1.8 默认开启了同步锁,但是建立在开启逃逸分析的基础上。
-XX:+EliminateLocks #开启同步锁消除(JVM默认状态)
-XX:-EliminateLocks #关闭同步锁消除
我们用一个例子来演示
@Test
public void testLock(){
long t1 = System.currentTimeMillis();
for (int i = 0; i < 100_000_000; i++) {
locketMethod();
}
long t2 = System.currentTimeMillis();
System.out.println("耗时:"+(t2-t1));
}
public static void locketMethod(){
EscapeAnalysis escapeAnalysis = new EscapeAnalysis();
synchronized(escapeAnalysis) {
escapeAnalysis.obj2="abcdefg";
}
}
上面这个EscapeAnalysis没有发生逃逸,JVM默认开启了同步锁消除。我们做几组试验对比
(1) 手动注释掉synchronized锁,直接运行,未设置JVM参数

(2) 保留synchronized锁,直接运行,未设置JVM参数

(3) 设置JVM参数,关闭锁消除,再次运行
java -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:-EliminateLocks


(4) 设置JVM参数,开启锁消除,再次运行
java -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+EliminateLocks

通过上述4组参照试验对比,可以得出,JDK1.8默认开启了锁消除,如果关闭锁消除,那么锁将非常消耗性能。
标量:不能被进一步分解的变量,如基础数据类型和对象的引用。
聚合量:能够被分解的变量,比如对象。
如果一个对象没有发生逃逸,可以将成员变量拆分成标量,就可以不用创建它,在栈或者寄存器中直接创建成员标量,这就叫标量替换。节省内存,提升了应用程序的性能。
JDK1.8默认开启了标量替换,也同样建立在逃逸分析的基础上
JVM相关参数
@Test
public void testScalarReplace(){
long t1 = System.currentTimeMillis();
for (int i = 0; i < 100_000_000; i++) {
scalarReplace();
}
long t2 = System.currentTimeMillis();
System.out.println("耗时:"+(t2-t1));
}
public static void scalarReplace(){
User user=new User();
user.setId(1);
user.setName("hello");
}
上面这个User没有发生逃逸,JVM默认开启了标量替换。我们做几组试验对比。
(1) 直接运行,未设置JVM参数

(2) 设置JVM参数,关闭标量替换
java -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:-EliminateAllocations

(3) 设置JVM参数,开启标量替换
java -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+EliminateAllocations

通过上述3组参照试验对比,可以得出,JDK1.8默认开启了标量替换,如果关闭标量替换,那么直接进行对象分配将非常影响性能。
将原本应当分配在堆上的对象分配到栈内存上,这样可以减少堆内存的使用,从而减少GC的频率。
首先,我们需要先了解决Java对象是如何分配内存的。

栈上分配是JVM提供的一项优化技术。
其思路是:
栈上分配是基于逃逸分析和标量替换的,所以必须开启逃逸分析和标量替换,当然JDK1.8是默认都是开启的。
逃逸分析和标量替换前面已经介绍了。

栈上分配时基于逃逸分析+标量替换,因此我们只要关闭逃逸分析或者标量替换任一一项,即可关闭栈上分配。
我们继续使用前面标量替换的demo。
java -Xmx15m -Xms15m -XX:+PrintFlagsFinal -XX:+PrintGCDetails -XX:-UseTLAB -XX:-DoEscapeAnalysis #关闭栈上分配

java -Xmx15m -Xms15m -XX:+PrintFlagsFinal -XX:+PrintGCDetails -XX:-UseTLAB -XX:+DoEscapeAnalysis #开启栈上分配

通过对比,我们可以看到
java -XX:+PrintFlagsFinal #输出打印所有参数jvm参数



