热点探测 (Hot Spot Detection)
当虚拟机发现某个方法或者代码块运行特别频繁,就会把这些代码认定为 “热点代码”(Hot Spot Code)。
判断代码是不是热点代码的这种行为被称为 “热点探测”(Hot Spot Detection) ,有两种热点代码判断的方法:
1. 基于采样的热点探测(Sample Based Hot Spot Code Detection)
采用虚拟机周期性检查哥哥线程的调用栈顶,如果发现某个方法经常出现在栈顶,那么这个方法就是“热点方法”。基于采样的热点探测的好处就是简单、高效,还很容易获取方法的调用关系(将调用堆栈展开即可),缺点就是很难准确的描述一个方法的热度,容易受到线程阻塞或者别的外籍因素影而骚乱热点探测。
2. 基于计数器的热点探测(Counter Based Hot Spot Code Detection)
采用这种方法的虚拟机会为 每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认为 它是“热点方法”。这种统计方法实现起来要麻烦一些,需要为每个方法建立并维护计数器,而且不能 直接获取到方法的调用关系。但是它的统计结果相对来说更加精确严谨。
HotSpot 虚拟机中采用的是基于计数器的热点探测方法,有一下两类计数器:
- 方法调用计数器(Invocation Counter),服务器模式默认 10000 次
- 回边计数器(Back Edge Counter),服务器模式默认 10700 次
方法内联
方法内敛的优化是把目标方法的代码复制到调用的方法之内,避免发生真实的方法调用。
- 热点探测技术触发
- 方法体大小限制
- 使用方法内联提升性能
测试代码:
/** * VM Args: * -XX:+PrintCompilation 控制台打印编译过程信息 * -XX:+UnlockDiagnosticVMOptions 解锁对 JVM 进行诊断的选项参数,默认关闭 * -XX:+PrintInlining 打印方法内联 * @author Administrator */ public class MethodInliningTest { public static void main(String[] args) { testInline(args); } public static int add(int a, int b) { return a + b; } public static void testInline(String[] args) { for (int i = 0; i < 1000000; i++) { add(i,i+1); } } }
查看打印信息,当前代码触发方法内联
逃逸分析(Escape Analysis)
**逃逸分析(Escape Analysis)**是目前Java虚拟机中比较前沿的优化技术,它与类型继承关系分析一样,并不是直接优化代码的手段,而是为其他优化措施提供依据的分析技术。
逃逸分析的基本原理是: 分析对象动态作用域,当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,这种称为方法逃逸;甚至还有可能被外部线程访问到,譬如赋值给可以在其他线程中访问的实例变量,这种称为线程逃逸;从不逃逸、方法逃逸到线程逃逸,称为对象由低到高的不同逃逸程度。
栈上分配(Stack Allocations)
在Java虚拟机中,Java堆上分配创建对象的内存空间几乎是 Java程序员都知道的常识,Java堆中的对象对于各个线程都是共享和可见的,只要持有这个对象的引用,就可以访问到堆中存储的对象数据。虚拟机的垃圾收集子系统会回收堆中不再使用的对象,但回收动作无论是标记筛选出可回收对象,还是回收和整理内存,都需要耗费大量资源。如果确定一个对象不会逃逸出线程之外,那让这个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,完全不会逃逸的局部对象和不会逃逸出线程的对象所占的比例是很大的,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,垃圾收集子系统的压力将会下降很多。栈上分配可以支持方法逃逸,但不能支持线程逃逸。
标量替换(Scalar Replacement)
若一个数据已经无法再分解成更小的数据来表示了,Java虚拟机中的原始数据类型(int、long等数值类型及reference类型等)都不能再进一步分解了,那么这些数据就可以被称为标量。相对的,如果一个数据可以继续分解,那它就被称为聚合量(Aggregate),Java 中的对象就是典型的聚合量。如果把一个Java对象拆散,根据程序访问的情况,将其用到的成员变量恢复为原始类型来访问,这个过程就称为标量替换。假如逃逸分析能够证明一个对象不会被方法外部访问,并且这个对象可以被拆散,那么程序真正执行的时候将可能不去创建这个对象,而改为直接创建它的若干个被这个方法使用的成员变量来代替。将对象拆分后,除了可以让对象的成员变量在栈上 (栈上存储的数据,很大机会被虚拟机分配至物理机器的高速寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创建条件。标量替换可以视作栈上分配的一种特例,实现更简单(不用考虑整个对象完整结构的分配),但对逃逸程度的要求更高,它不允许对象逃逸出方法范围内。