回答
逃逸分析(Escape Analysis)是 JVM 的一项优化技术。JVM 通过分析代码中对象的生命周期和作用域,判断某个对象是否会逃逸出某个作用域(如方法或者线程),如果 JVM 可以确定该对象不会逃逸,则可以做一些优化策略,例如栈上分配、标量替换、同步消除,从而提升程序的执行效率,降低内存分配和垃圾回收的负担。
详解
逃逸分析详解
- 逃逸:如果一个对象在创建后能够在当前作用域之外被访问或引用,就称为这个对象“逃逸”了。逃逸有两种:
- 方法逃逸:对象被方法外部使用,例如,将对象作为返回值或者传递给其他方法,这种情况下对象必须在堆上分配。
- 线程逃逸:对象被其他线程访问,例如对象被赋值给一个静态变量或者实例变量,这种情况下也需要在堆上分配。
- 非逃逸:如果一个对象仅在当前作用域内使用,且不会被外部引用,则该对象为“非逃逸”对象。
对于非逃逸对象,JVM 有三个优化手段:栈上分配、同步消除、标量替换。
栈上分配
通常情况下,Java 中的对象都是在堆上分配。但是当 JVM 通过逃逸分析确认某个对象不会逃逸出方法的作用域,则会考虑将该对象分配在栈上。
当对象满足以下条件之一,通常会被分配到栈上:
- 对象没有被返回给方法外部。
- 对象没有被赋值给类的成员变量或静态变量。
- 对象没有被其他线程访问。
由于栈属于线程私有,当方法执行完成后,栈帧弹出,栈上分配的对象内存也随之释放。因此,在栈上分配的对象是不需要被垃圾回收器回收,减轻了 GC 的压力。例如:
public class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
public class EscapeAnalysisTest {
public static void main(String[] args) throws Exception {
for (int i = 0 ; i < 1000 ; i++) {
createPoint();
}
}
public static void createPoint() {
Point point = new Point(1, 2);
System.out.println(point.x + ", " + point.y);
}
}
在 createPoint()
方法中,point
对象只在方法内使用,且也没有被其他线程使用,则 JVM 可能会将该对象在栈上分配。
标量替换
标量替换的核心思想是将一个对象分解为多个独立的基本类型变量(标量),从而避免为整个对象分配内存。
在计算机科学中,标量(Scalar)是指不能再分解的基本数据类型,例如整数、浮点数等等。还有一个聚合量(Aggregate)则是由多个标量组成的复杂数据类型,如数组、对象等等。
当 JVM 通过逃逸分析确定某个对象不会逃逸出方法范围,且对象可以分解为多个独立的基本类型时,JVM就会考虑进行标量替换。
标量替换通常在以下情况下发生:
- 对象的字段在方法内部可以独立操作。
- 对象的字段可以被拆解为局部变量。
举个例子说明下。
public class EscapeAnalysisTest2 {
public static void main(String[] args) throws Exception {
int result = calculate();
System.out.println(result);
}
private static int calculate() {
Point point = new Point(1, 2);
return point.x + point.y;
}
}
如果没有进行标量替换,point
对象会在 calculate()
中创建并分配内存(可能是堆或者栈),并在访问 point.x
和 point.y
时,通过对象引用来操作这些字段。
但是,通过逃逸分析,JVM 可以确定 point
不会逃逸出 calculate()
且 point 对象是可以被分解为两个 int 基本数据类型,所以 JVM 会进行标量替换。具体来说,JVM会将point
对象的字段x
和y
替换为两个独立的局部变量,从而避免创建Point
对象。优化代码如下:
public static int calculate() {
int x = 1;
int y = 2;
return x + y;
}
在这种情况下,JVM不会为point
对象分配内存,而是直接在栈或寄存器中操作x
和y
这两个标量。
所以,标量替换有两个优势:
- 可以避免分配整个对象的内存,从而减少堆或栈上的内存使用,降低 GC 的压力。
- 可以直接在寄存器或栈上操作标量,减少对内存的访问次数,从而提升程序的运行效率。
同步消除
同步消除,也叫做锁消除,其主要目标是通过逃逸分析来确定某些锁操作是否有必要。
如果JVM能够确认某个锁对象不会被其他线程访问,那么它可以安全地消除这些不必要的同步操作,从而减少上下文切换、锁竞争等开销,提高程序的执行效率。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。