写点什么

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

用户头像
冰河
关注
发布于: 2020 年 09 月 21 日
【性能优化】面试官:Java中的对象都是在堆上分配的吗?

写在前面


从开始学习 Java 的时候,我们就接触了这样一种观点:Java 中的对象是在堆上创建的,对象的引用是放在栈里的,那这个观点就真的是正确的吗?如果是正确的,那么,面试官为啥会问:“Java 中的对象就一定是在堆上分配的吗?”这个问题呢?看来,我们从接触 Java 就被灌输的这个观点值得我们怀疑。

关于面试题

标题中的面试题为:Java 中的对象都是在堆上分配的吗?


面试官这样问,有些小伙伴心里会想:我从一开始学习 Java 时,就知道了:Java 中的对象是在堆上创建的,对象的引用是存储到栈中的,那 Java 中的对象是在堆上分配的啊!难道不是吗?



如果你这样回答,就会被直接 Pass 掉。

或许有些小伙伴还是不太明白,那我们继续往下看。

面试题答案


首先,我们先给出这个题目的答案,这里我先简短的回答下这个面试题,后续我们会进行相关分析。


你可以这样回答:Java 中的对象不一定是在堆上分配的,因为 JVM 通过逃逸分析,能够分析出一个新对象的使用范围,并以此确定是否要将这个对象分配到堆上。如果 JVM 发现某些对象没有逃逸出方法,就很有可能被优化成在栈上分配。


这里,我们接触了一个新名词:逃逸分析。相信很多小伙伴不是很明白,那我们继续往下看。


逃逸分析

逃逸分析的概念

先以官方的形式来说下什么是逃逸分析。逃逸分析就是:一种确定指针动态范围的静态分析,它可以分析在程序的哪些地方可以访问到指针。


在 JVM 的即时编译语境下,逃逸分析将判断新建的对象是否逃逸。即时编译判断对象是否逃逸的依据:一种是对象是否被存入堆中(静态字段或者堆中对象的实例字段),另一种就是对象是否被传入未知代码。


直接说这些概念,确实有点晕啊,那我们就来两个示例。


对象逃逸示例


一种典型的对象逃逸就是:对象被复制给成员变量或者静态变量,可能被外部使用,此时变量就发生了逃逸。


我们可以用下面的代码来表示这个现象。


/** * @author binghe * @description 对象逃逸示例1 */public class ObjectEscape{    private User user;    public void init(){        user = new User();    }}
复制代码


在 ObjectEscape 类中,存在一个成员变量 user,我们在 init()方法中,创建了一个 User 类的对象,并将其赋值给成员变量 user。此时,对象被复制给了成员变量,可能被外部使用,此时的变量就发生了逃逸。


另一种典型的场景就是:对象通过 return 语句返回。如果对象通过 return 语句返回了,此时的程序并不能确定这个对象后续会不会被使用,外部的线程可以访问到这个变量,此时对象也发生了逃逸。


我们可以用下面的代码来表示这个现象。


/** * @author binghe * @description 对象逃逸示例2 */public class ObjectReturn{    public User createUser(){        User user = new User();        return user;    }}
复制代码


给出两个示例,相信小伙伴们对 JVM 的逃逸分析多少有点了解了吧,没错,JVM 通过逃逸分析,能够分析出新对象的使用范围,从而决定新对象是否要在堆上进行分配。


还没完,我们继续看下逃逸分析的优点,以便于小伙伴们能够更好的理解逃逸分析。

逃逸分析的优点


逃逸分析的优点总体上来说可以分为三个:对象可能分配在栈上、分离对象或标量替换、消除同步锁。我们可以使用下图来表示。


对象可能分配在栈上


JVM 通过逃逸分析,分析出新对象的使用范围,就可能将对象在栈上进行分配。栈分配可以快速地在栈帧上创建和销毁对象,不用再将对象分配到堆空间,可以有效地减少 JVM 垃圾回收的压力。


分离对象或标量替换


当 JVM 通过逃逸分析,确定要将对象分配到栈上时,即时编译可以将对象打散,将对象替换为一个个很小的局部变量,我们将这个打散的过程叫做标量替换。将对象替换为一个个局部变量后,就可以非常方便的在栈上进行分配了。


同步锁消除


如果 JVM 通过逃逸分析,发现一个对象只能从一个线程被访问到,则访问这个对象时,可以不加同步锁。如果程序中使用了 synchronized 锁,则 JVM 会将 synchronized 锁消除。


这里,需要注意的是:这种情况针对的是 synchronized 锁,而对于 Lock 锁,则 JVM 并不能消除。


要开启同步消除,需要加上 -XX:+EliminateLocks 参数。因为这个参数依赖逃逸分析,所以同时要打开 -XX:+DoEscapeAnalysis 选项。


所以,并不是所有的对象和数组,都是在堆上进行分配的,由于即时编译的存在,如果 JVM 发现某些对象没有逃逸出方法,就很有可能被优化成在栈上分配。

重磅福利


微信搜一搜【冰河技术】微信公众号,关注这个有深度的程序员,每天阅读超硬核技术干货,公众号内回复【PDF】有我准备的一线大厂面试资料和我原创的超硬核 PDF 技术文档,以及我为大家精心准备的多套简历模板(不断更新中),希望大家都能找到心仪的工作,学习是一条时而郁郁寡欢,时而开怀大笑的路,加油。如果你通过努力成功进入到了心仪的公司,一定不要懈怠放松,职场成长和新技术学习一样,不进则退。如果有幸我们江湖再见!


另外,我开源的各个 PDF,后续我都会持续更新和维护,感谢大家长期以来对冰河的支持!!

写在最后


如果你觉得冰河写的还不错,请微信搜索并关注「 冰河技术 」微信公众号,跟冰河学习高并发、分布式、微服务、大数据、互联网和云原生技术,「 冰河技术 」微信公众号更新了大量技术专题,每一篇技术文章干货满满!不少读者已经通过阅读「 冰河技术 」微信公众号文章,吊打面试官,成功跳槽到大厂;也有不少读者实现了技术上的飞跃,成为公司的技术骨干!如果你也想像他们一样提升自己的能力,实现技术能力的飞跃,进大厂,升职加薪,那就关注「 冰河技术 」微信公众号吧,每天更新超硬核技术干货,让你对如何提升技术能力不再迷茫!



发布于: 2020 年 09 月 21 日阅读数: 87
用户头像

冰河

关注

公众号:冰河技术 2020.05.29 加入

Mykit系列开源框架发起者、核心架构师和开发者,《海量数据处理与大数据技术实战》与《MySQL开发、优化与运维实战》作者。【冰河技术】微信公众号作者。

评论

发布
暂无评论
【性能优化】面试官:Java中的对象都是在堆上分配的吗?