JVM 内存优化

逃逸分析

逃逸分析,是 Java 虚拟机中的一种优化技术,但它并不是直接优化代码,而是为其他优化手段提供优化依据的分析技术。

逃逸分析的基本行为就是分析对象的动态作用域:

  • 当一个对象在方法中被定义后,它可能被外部方法引用,例如作为参数传递到了其他方法中,这称之为方法逃逸;
  • 如果被外部线程访问到,比如赋值给类变量或者可以在其他线程中访问的实例变量,这称之为线程逃逸。

如果可以证明一个对象不会发生方法逃逸和线程逃逸(这意味着,别的方法和线程无法通过任何途径访问到这个对象),那么,虚拟机可能会为这个变量进行一些高效的优化,优化手段有以下几种。

栈上分配

在我们的常识中,几乎所有的对象都是在 Java 堆上分配和创建的,Java 堆中的对象是线程共享的,只要持有这个对象的引用,就可以访问堆中存储的这个对象的数据。虚拟机的垃圾收集,可以回收堆中不再使用的对象,但是,不管是回收还是筛选可回收对象,或者是回收和整理内存,都是需要消耗时间的。

如果可以确定一个对象不会发生方法逃逸,那么可以让这个对象直接在栈上分配,这样对象占用的内存就可以随着栈帧出栈而销毁。在很多应用中,不会逃逸的局部变量所占的比例很大,如果可以使用栈上分配,那么大量的对象就会随着方法的结束而自动销毁了,这样垃圾收集系统的压力会小很多。

同步消除

如果逃逸分析能够确定一个变量不会发生线程逃逸,那么这个变量的读写就不会发生线程竞争,对这个变量实施的同步措施就可以消除了。

标量替换

标量:一个数据无法再分解为更小的数据来表示了,Java 虚拟机中的原始数据类型 byte、short、int、long、boolean、char、float、double 以及 reference 类型等,都不能再进一步分解了,这些就可以称为标量。 聚合量:如果一个数据可以继续分解,就称为聚合量。对象就是最典型的聚合量。 如果把一个 Java 对象拆散,根据程序访问的情况,将其使用到的成员变量恢复到原始类型来访问,就叫标量替换。

如果逃逸分析可以证明一个对象不会被外部访问,并且这个对象可以拆散的话,那程序真正执行时将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来替代。将对象拆分后,除了可以让对象的成员变量在栈上分配和读写外(栈上存储的数据,有很大概率会被虚拟机分配至物理机器的高速寄存器中存储),还可以为后续的进一步优化手段创造条件。