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

开发 后端
作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。

 [[343146]]

作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:

https://github.com/sunshinelyz/mykit-delay

写在前面

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

关于面试题

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

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

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

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

面试题答案

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

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

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

[[343147]]

逃逸分析

逃逸分析的概念

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

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

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

[[343148]]

对象逃逸示例

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

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

  1. /** 
  2.  * @author binghe 
  3.  * @description 对象逃逸示例1 
  4.  */ 
  5. public class ObjectEscape{ 
  6.     private User user
  7.     public void init(){ 
  8.         user = new User(); 
  9.     } 

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

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

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

  1. /** 
  2.  * @author binghe 
  3.  * @description 对象逃逸示例2 
  4.  */ 
  5. public class ObjectReturn{ 
  6.     public User createUser(){ 
  7.         User user = new User(); 
  8.         return user
  9.     } 

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

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

逃逸分析的优点

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

对象可能分配在栈上

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

分离对象或标量替换

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

同步锁消除

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

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

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

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

本文转载自微信公众号「冰河技术」,可以通过以下二维码关注。转载本文请联系冰河技术公众号。 

 

责任编辑:武晓燕 来源: 冰河技术
相关推荐

2018-05-15 16:01:53

对象内存JVM

2022-07-15 08:22:42

对象符串键Symbol

2021-03-16 07:13:07

Java对象存储

2021-08-02 08:34:20

React性能优化

2020-07-28 00:58:20

IP地址子网TCP

2019-11-21 08:40:44

面试官优化性能

2020-11-03 07:00:15

性能优化程序员

2021-03-24 10:25:24

优化VUE性能

2021-05-08 08:35:33

Webpack前端性能

2023-08-11 17:13:39

JavaScrip

2022-11-19 18:18:22

Spring架构

2015-08-13 10:29:12

面试面试官

2022-10-10 12:31:37

服务器性能

2021-09-08 08:06:57

Redis原子性数据类型

2024-03-07 17:21:12

HotSpotJVMHot Code

2023-03-08 07:46:53

面试官优化结构体

2020-08-03 07:04:54

测试面试官应用程序

2020-03-10 08:01:05

Java堆内存线程共享

2022-07-12 12:05:22

JavaSemaphore

2023-07-11 16:01:47

共享数据开发
点赞
收藏

51CTO技术栈公众号