热点
在典型的程序中,只有一小段代码会经常执行,而正是这种代码会明显影响整个应用程序的性能。这样的代码段称为HotSpots(热点)。如果某段代码仅执行一次,那么编译该段代码会很浪费精力,而解释字节码会更快。但是,如果该节是一个HotSpots并且执行了多次,则JVM会编译它。例如,如果某个方法被多次调用,则编译代码所需的额外周期将被生成的更快的二进制文件抵消。此外,JVM运行特定方法或循环的次数越多,JVM收集到的信息越多,它们可以进行各种优化,从而生成更快的二进制文件。
让我们考虑以下代码-
for(int i = 0 ; I <= 100; i++) {
System.out.println(obj1.equals(obj2)); //two objects
}
如果解释此代码,则解释器将为每次迭代推导obj1类。这是因为Java中的每个类都有一个.equals()方法,该方法是从Object类扩展而来的,可以重写。因此,即使obj1是每次迭代的字符串,推论(得出结果)仍然会完成。
但是,实际上会发生的情况是,JVM将注意到,对于每次迭代,obj1都是String类的,因此,它将直接生成与String类的.equals()方法相对应的代码。因此,将不需要查找,并且编译后的代码将执行得更快。仅当JVM知道代码的行为方式时,这种行为才可能发生。因此,它在编译代码的某些部分之前会等待。
以下是另一个示例-
int sum = 7;
for(int i = 0 ; i <= 100; i++) {
sum += i;
}
对于每个循环,解释器都会从内存中获取“sum”的值,并在其中添加“i”,然后将其存储回内存中。内存访问是一项昂贵的操作,通常需要多个CPU周期。由于此代码多次运行,因此它是一个HotSpot。JIT将编译此代码并进行以下优化。 “sum”的本地副本将存储在特定于特定线程的寄存器中。所有操作都将对寄存器中的值完成,并且当循环完成时,该值将被写回到存储器中。如果其他线程也正在访问该变量怎么办?由于其他线程正在对变量的本地副本进行更新,因此它们将看到旧的值。在这种情况下,需要线程同步。一个非常基本的同步原语是将“sum”声明为volatile。现在,在访问变量之前,线程将刷新其本地寄存器并从内存中获取值。访问后,该值将立即写入存储器。
以下是JIT编译器完成的一些常规优化-
- 方法内联
- 消除死代码
- 优化呼叫站点的启发法
- 不断折叠