逃逸分析(Escape Analysis)是一种静态程序分析技术,主要用于判定对象的可见范围(Visibility)与生命周期(Lifetime)。该技术是现代即时编译器实现局部化优化、提升内存使用效率、降低同步成本的基础。

通俗来说,逃逸分析的核心在于回答这样一个问题:某个对象是否可能“逃逸”出它所创建的方法或线程作用域?

逃逸分析的结果通常分为三种情形。

1)未逃逸(No Escape):对象完全局限在当前方法内,既未作为返回值,也未传递到其他线程或方法。

2)方法逃逸(Method Escape):对象作为参数传递到其他方法中,虽然不一定跨线程访问,但由于编译器无法确定外部方法的副作用,因此仍视为潜在逃逸。

3)线程逃逸(Thread Escape):对象的引用被赋值给共享变量,或作为任务传递给其他线程。这类对象无法进行逃逸相关优化,必须保留其线程安全保障。

下面代码的是对象未逃逸的例子:

// add方法中创建了一个名为NonEscapeObject的对象。
// 这个对象仅在add方法中使用,用于计算两个整数的和。
// 这个对象没有作为方法的返回值、赋值给全局变量或作为参数传递给其他方法。
// 因此它被认为是未逃逸的。
int add(int a, int b) { NonEscapeObject o = new NonEscapeObject(a, b); return obj.getX() + obj.getY();
} class NonEscapeObject { private int x; private int y;
}

基于逃逸分析的信息,即时编译器可以执行一些优化,例如同步锁消除(Synchronization Elimination)、标量替换(Scalar Replacement)和栈上分配(Stack allcotion)。

同步锁消除

线程同步是一个相对耗时的过程,如果逃逸分析能确定一个共享变量不会逃出线程,无法被其他线程访问,那这个共享变量的读写肯定就不会有竞争,对这个变量实施的同步措施也就可以消除掉。

// 由于obj没有逃逸出doSomething()方法的范围,编译器可以进行逃逸分析并确定该对象不会被其他线程访问。
// 在逃逸分析确定obj对象不会逃逸的情况下,编译器可以消除对该对象的同步锁操作。
void doSomething() { Object obj = new Object(); synchronized (obj) { // 对obj进行一些操作 // ...
}
}

标量替换

标量(scalar)是指一个无法再分解成更小的数据的数据。Java 中的基本数据类型就是标量。相对的Java 中的对象就是聚合量(Aggregate),因为它可以分解成其他聚合量和标量。

如果经过逃逸分析,发现一个对象并没有逃逸出方法和线程,那么就可以将这个对象视为一组标量值。这样,Java虚拟机就可以将这个对象的所有字段视为局部变量,从而在栈上分配这些局部变量,而不是在堆上分配整个对象,这样可以减少堆内存的占用。

void test() {

   Point point = new Point(1,2);

   System.out.println("point.x" + point.x + ";point.y" + point.y);
} class Point { private int x; private int y;
}

假设有一个Point对象,包含x和y两个字段。如果经过逃逸分析,发现这个Point对象并没有逃逸出方法,那么Java虚拟机就可以将这个Point对象视为两个独立的标量值x和y,然后在栈上分配这两个值,而不是在堆上分配整个Point对象。

void test() {
int x = 1; int y = 2; System.out.println("point.x = " + x + "; point.y=" + y);
}

栈上分配

Java的对象是在堆上分配的,Java虚拟机对堆内存的垃圾对象回收是一个耗时的过程。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁,垃圾收集系统的压力将会小很多。

虽然逃逸分析理论上支持将非逃逸对象直接分配到栈上,从而避免堆内存开销与垃圾回收成本,但如HotSpot虚拟机并未真正实现物理意义上的栈上分配。原因在于:在支持线程抢占、嵌套调用、异常恢复与栈帧迁移(如逃逸到堆)等复杂运行时语义的情况下,栈上对象生命周期管理的正确性将变得异常困难,容易引发并发可见性等问题。因此,Hotspot虚拟机并没有进行实际的栈上分配,而是使用了标量替换这一技术。

尽管逃逸分析为即时编译器带来了多种激进优化的可能,但它本身也是一项计算复杂度较高的静态分析技术。在分析过程中,编译器需要对对象的引用路径进行全程追踪,判断其是否会被其他线程访问、是否会通过方法返回或赋值跨出当前作用域。特别是在存在复杂控制流、间接调用或反射的情况下,分析准确性与代价都将急剧上升。

这种计算成本并非微不足道:在代码编译时长与运行时性能收益之间,并不总是呈现出正向关系。在某些边缘场景中,逃逸分析所带来的优化甚至可能因分析开销过大、代码形态不佳(例如过度拆箱、短生命周期对象)而无法收回性能投入。因此,Java虚拟机会采用热点代码触发机制,仅对高频路径进行逃逸分析,以期在收益与成本之间实现动态平衡。

虽然这项技术并不十分成熟,但是它也是即时编译器优化技术中一个十分重要的手段。

未完待续

很高兴与你相遇!如果你喜欢本文内容,记得关注哦!

new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析的更多相关文章

  1. 《MORE EFFECTIVE C++》条款27 要求或者禁止对象分配在堆上

    1. 要求对象分配在堆上 临时对象一般是存在于栈中的,或者是静态对象存在于常量存储区的.那么当创建一个这样的对象的时候,一般是需要隐式或显式地调用构造函数,在销毁的时候调用析构函数的.可以从这方面入手 ...

  2. 如何判断一个C++对象是否在堆上(通过GetProcessHeaps取得所有堆,然后与对象地址比较即可),附许多精彩评论

    在帖子如何判断一个C++对象是否在堆栈上 中, 又有人提出如何判断一个C++对象是否在堆上. 其实我们可以参照那个帖子的方法类似实现,我们知道堆就是Heap,在windows上我们可以通过GetPro ...

  3. Java对象已死吗 深入理解Java虚拟机笔记

    1.引用计数器法 给每个对象设置一个计数器,每当有一个引用就给计数器的值+1,引用时小时就减一,当计数器值为0是就可以回收掉了. 主流虚拟机都没有使用这种算法,循环依赖问题 2.可达性分析: 思路是通 ...

  4. 【性能优化】面试官:Java中的对象都是在堆上分配的吗?

    写在前面 从开始学习Java的时候,我们就接触了这样一种观点:Java中的对象是在堆上创建的,对象的引用是放在栈里的,那这个观点就真的是正确的吗?如果是正确的,那么,面试官为啥会问:"Jav ...

  5. 面试官:Java中对象都存放在堆中吗?你知道逃逸分析?

    面试官:Java虚拟机的内存分为哪几个区域? 我(微笑着):程序计数器.虚拟机栈.本地方法栈.堆.方法区 面试官:对象一般存放在哪个区域? 我:堆. 面试官:对象都存放在堆中吗? 我:是的. 面试官: ...

  6. 逃逸分析与栈、堆分配分析 escape_analysis

    小结: 1.当形参为 interface 类型时,在编译阶段编译器无法确定其具体的类型.因此会产生逃逸,最终分配到堆上. 2.The construction of a value doesn't d ...

  7. Java中对象并不是都在堆上分配内存的

    转(https://blog.51cto.com/13906751/2153924) 前段时间,给星球的球友们专门码了一篇文章<深入分析Java的编译原理>,其中深入的介绍了Java中的j ...

  8. Java中的对象都是在堆上分配的吗?

    作者:LittleMagic https://www.jianshu.com/p/8377e09971b8 为了防止歧义,可以换个说法: Java对象实例和数组元素都是在堆上分配内存的吗? 答:不一定 ...

  9. C++:在堆上创建对象,还是在栈上?

    这篇文章来自于一次讨论:http://www.devbean.net/2013/01/qt-study-road-2-model-view/#comment-17532.关于究竟是在堆上还是在栈上创建 ...

  10. Java堆外内存之一:堆外内存场景介绍(对象池VS堆外内存)

    最近经常有人问我在Java中使用堆外(off heap)内存的好处与用途何在.我想其他面临几样选择的人应该也会对这个答案感兴趣吧. 堆外内存其实并无特别之处.线程栈,应用程序代码,NIO缓存用的都是堆 ...

随机推荐

  1. Spring boot 项目集成Redisson抛异常 NoClassDefFoundError: Lorg/nustaq/serialization/FSTConfiguration

    Spring boot 项目集成Redisson做分布式锁的时候,抛异常 NoClassDefFoundError: Lorg/nustaq/serialization/FSTConfiguratio ...

  2. 【Spring Boot】ActiveMQ 设置访问密码

    Apache ActiveMQ是Apache出品,是最流行的,能力很强的开源消息总线.默认情况下,程序连接ActiveMQ是不需要密码的,为了安装起见,需要设置密码,提高安全性.本文分享如何设置访问A ...

  3. 一个老程序员, 两个小时能用corsur做出什么样的东西

    背景 最近cosur太火了, 很多没开发背景的人也直接说0基础建站了, 互联网项目的门槛越高越低. 第一次看到一个行业拼命卷自己的.  作为一个16年的老程序员了, 肯定得试试这款颠覆性的产品. 在上 ...

  4. python C3算法

    Python MRO C3算法是python当中计算类继承顺序的一个算法,从python2.3以后就一直使用此算法了. c3 linearization算法称为c3线性化算法 C3算法原理 首先定义几 ...

  5. cuda grid block size

    编译命令:nvcc hello.cu -o hello 运行:./hello #include <stdio.h> __global__ void helloWorldKernel() { ...

  6. AI 赋能指标管理分析,开启企业数智领航时代

    以下为本次分享的回顾: 在大数据时代,企业数字化转型的核心目标在于让数据发挥真正的价值.从数据报表到分析平台,再到日常取数,企业所依赖的不仅仅是数据本身,而是通过数据所呈现出对业务的分析.业务的查看以 ...

  7. 3节点开启大数据时代:EasyMR助力中小企业轻装上阵、国产转型

    在数字化浪潮中, 数据已成为中小企业竞争力的核心要素.然而,受限于预算.技术和运维能力,众多中小企业在建设大数据平台时常陷入"建不起.用不好"的困境. 传统大数据平台通常起步门槛高 ...

  8. [Java/字节流/BytesReader] 核心源码精讲: ByteArrayInputStream(字节数组输入流)

    概述 : ByteArrayInputStream(字节数组输入流) 简介 字节数组输入流在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中. java.io.ByteArra ...

  9. 前端开发系列068-JQuery篇之框架的选择器

    一.jQuery选择器说明 jQuery 最核心的组成部分就是选择器引擎.它完全继承了 CSS 的风格,可以对 DOM 元 素的标签名.属性名.状态等进行快速准确的选 择,而且不必担心浏览器的兼容性, ...

  10. win10 多用户登陆

    win10 多用户登陆 一般的直接下载就可以用了. 核心参考链接github 支持 1903 支持最新版本可以需要这个 1903支持项参考页面 上述页面的下载文件页面1903支持页面 关于上述链接下载 ...