前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Java 对象都是在堆上分配内存吗?

Java 对象都是在堆上分配内存吗?

作者头像
用户1516716
发布于 2019-12-10 10:17:43
发布于 2019-12-10 10:17:43
1K00
代码可运行
举报
文章被收录于专栏:A周立SpringCloudA周立SpringCloud
运行总次数:0
代码可运行

来源:LittleMagic jianshu.com/p/8377e09971b8

为了防止歧义,可以换个说法:Java对象实例和数组元素都是在堆上分配内存的吗? 答:不一定。满足特定条件时,它们可以在(虚拟机)栈上分配内存。

JVM内存结构很重要,多多复习

这和我们平时的理解可能有些不同。虚拟机栈一般是用来存储基本数据类型、引用和返回地址的,怎么可以存储实例数据了呢?这是因为Java JIT(just-in-time)编译器进行的两项优化,分别称作逃逸分析(escape analysis)标量替换(scalar replacement)。JIT是个复杂的话题,本文不赘述,看官如果想进一步了解的话,可以参考这篇文章,它里面提供了几篇有用的参考资料。

注意看一下JIT的位置

中文维基上对逃逸分析的描述基本准确,摘录如下:

在编译程序优化理论中,逃逸分析是一种确定指针动态范围的方法——分析在程序的哪些地方可以访问到指针。当一个变量(或对象)在子程序中被分配时,一个指向变量的指针可能逃逸到其它执行线程中,或是返回到调用者子程序。 如果一个子程序分配一个对象并返回一个该对象的指针,该对象可能在程序中被访问到的地方无法确定——这样指针就成功“逃逸”了。如果指针存储在全局变量或者其它数据结构中,因为全局变量是可以在当前子程序之外访问的,此时指针也发生了逃逸。 逃逸分析确定某个指针可以存储的所有地方,以及确定能否保证指针的生命周期只在当前进程或线程中。

简单来讲,JVM中的逃逸分析可以通过分析对象引用的使用范围(即动态作用域),来决定对象是否要在堆上分配内存,也可以做一些其他方面的优化。

以下的例子说明了一种对象逃逸的可能性。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  static StringBuilder getStringBuilder1(String a, String b) {
    StringBuilder builder = new StringBuilder(a);
    builder.append(b);
    return builder;   // builder通过方法返回值逃逸到外部
  }

  static String getStringBuilder2(String a, String b) {
    StringBuilder builder = new StringBuilder(a);
    builder.append(b);
    return builder.toString();  // builder范围维持在方法内部,未逃逸
  }

以JDK 1.8为例,可以通过设置JVM参数-XX:+DoEscapeAnalysis-XX:-DoEscapeAnalysis来开启或关闭逃逸分析(默认当然是开启的)。下面先写一个没有对象逃逸的例子。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class EscapeAnalysisTest {
  public static void main(String[] args) throws Exception {
    long start = System.currentTimeMillis();
    for (int i = 0; i < 5000000; i++) {
      allocate();
    }
    System.out.println((System.currentTimeMillis() - start) + " ms");
    Thread.sleep(600000);
  }

  static void allocate() {
    MyObject myObject = new MyObject(2019, 2019.0);
  }

  static class MyObject {
    int a;
    double b;

    MyObject(int a, double b) {
      this.a = a;
      this.b = b;
    }
  }
}

然后通过开启和关闭DoEscapeAnalysis开关观察不同。

  • 关闭逃逸分析
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
~ java -XX:-DoEscapeAnalysis EscapeAnalysisTest
76 ms
~ jmap -histo 26031
 num     #instances         #bytes  class name
----------------------------------------------
   1:       5000000      120000000  me.lmagics.EscapeAnalysisTest$MyObject
   2:           636       12026792  [I
   3:          3097        1524856  [B
   4:          5088         759960  [C
   5:          3067          73608  java.lang.String
   6:           623          71016  java.lang.Class
   7:           727          43248  [Ljava.lang.Object;
   8:           532          17024  java.io.File
   9:           225          14400  java.net.URL
  10:           334          13360  java.lang.ref.Finalizer
# ......
  • 开启逃逸分析
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
~ java -XX:+DoEscapeAnalysis EscapeAnalysisTest
4 ms
~ jmap -histo 26655
 num     #instances         #bytes  class name
----------------------------------------------
   1:           592       11273384  [I
   2:         90871        2180904  me.lmagics.EscapeAnalysisTest$MyObject
   3:          3097        1524856  [B
   4:          5088         759952  [C
   5:          3067          73608  java.lang.String
   6:           623          71016  java.lang.Class
   7:           727          43248  [Ljava.lang.Object;
   8:           532          17024  java.io.File
   9:           225          14400  java.net.URL
  10:           334          13360  java.lang.ref.Finalizer
# ......

可见,关闭逃逸分析之后,堆上有5000000个MyObject实例,而开启逃逸分析之后,就只剩下90871个实例了,不管是实例数还是内存占用都只有原来的2%不到。另外,如果把堆内存限制得小一点(比如加上-Xms10m -Xmx10m),并且打印GC日志(-XX:+PrintGCDetails)的话,关闭逃逸分析还会造成频繁的GC,开启逃逸分析就没有这种情况。这说明逃逸分析确实降低了堆内存的压力。

但是,逃逸分析只是栈上内存分配的前提,接下来还需要进行标量替换才能真正实现。

所谓标量,就是指JVM中无法再细分的数据,比如int、long、reference等。相对地,能够再细分的数据叫做聚合量。仍然考虑上面的例子,MyObject就是一个聚合量,因为它由两个标量a、b组成。通过逃逸分析,JVM会发现myObject没有逃逸出allocate()方法的作用域,标量替换过程就会将myObject直接拆解成a和b,也就是变成了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  static void allocate() {
    int a = 2019;
    double b = 2019.0;
  }

可见,对象的分配完全被消灭了,而int、double都是基本数据类型,直接在栈上分配就可以了。所以,在对象不逃逸出作用域并且能够分解为纯标量表示时,对象就可以在栈上分配。

JVM提供了参数-XX:+EliminateAllocations来开启标量替换,默认仍然是开启的。显然,如果把它关掉的话,就相当于禁止了栈上内存分配,只有逃逸分析是无法发挥作用的。在Debug版JVM中,还可以通过参数-XX:+PrintEliminateAllocations来查看标量替换的具体情况。

除了标量替换之外,通过逃逸分析还能实现同步消除(synchronization elision),当然它与本文的主题无关了。举个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  private void someMethod() {
    Object lockObject = new Object();
    synchronized (lockObject) {
      System.out.println(lockObject.hashCode());
    }
  }

lockObject这个锁对象的生命期只在someMethod()方法中,并不存在多线程访问的问题,所以synchronized块并无意义,会被优化掉:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  private void someMethod() {
    Object lockObject = new Object();
    System.out.println(lockObject.hashCode());
  }

累了,晚安晚安。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT牧场 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java虚拟机内存结构(JVM)
Java7及以前版本方法区位于永久代中。同时,永久代和堆是相互隔离的,但它们使用的物理内存是连续的。
chenchenchen
2022/01/05
5270
Java虚拟机内存结构(JVM)
深入探究JVM之对象创建及分配策略
Java是面向对象的语言,所谓“万事万物皆对象”就是Java是基于对象来设计程序的,没有对象程序就无法运行(8大基本类型除外),那么对象是如何创建的?在内存中又是怎么分配的呢?
夜勿语
2020/09/07
4140
【性能优化】面试官:Java中的对象和数组都是在堆上分配的吗?
https://github.com/sunshinelyz/mykit-delay
冰河
2020/10/29
2.1K0
【性能优化】面试官:Java中的对象和数组都是在堆上分配的吗?
java栈内存初始化,阿里面试官:小伙子,你给我说一下JVM对象创建与内存分配机制吧…
虚拟机遇到一条new指令(new关键字、对象的克隆、对象的序列化等)时,会先去检查这个指令的参数在常量池中定位到一个类的符号引用,并且这个符号引用代表的类是否应被加载过,如果没有那么就去加载该类
全栈程序员站长
2022/08/28
3260
java栈内存初始化,阿里面试官:小伙子,你给我说一下JVM对象创建与内存分配机制吧…
Java对象竟然会在栈上分配内存?
JVM中高深的优化技术,如同类继承关系分析,该技术并非直接去优化代码,而是一种为其他优化措施提供依据的分析技术。
JavaEdge
2021/03/23
6900
Java对象竟然会在栈上分配内存?
Java的对象一定是在堆上分配的嘛?谁这么说就直接用“逃逸分析”反驳他!
之前在和朋友聊天的时候,他突然问我什么是“逃逸分析”。说实话当时我还真不太能完整的讲出什么是逃逸分析。这玩意虽然我看八股的时候经常遇见,但之前还真没专项学习过。因此我们今天来完整的介绍一下什么是逃逸分析。
程序员牛肉
2025/01/22
650
Java的对象一定是在堆上分配的嘛?谁这么说就直接用“逃逸分析”反驳他!
JVM-彻底搞懂 逃逸分析&标量替换
通过上图的对象分配流程,我们可以知道逃逸分析是发生在第一步判断对象是否可以在栈上分配的时候, 在栈上分配的目的是为了减少将对象分配到堆上的概率,节约堆内存,减少GC压力。
小小工匠
2021/08/17
2K0
面试官:是不是所有的对象和数组都会在堆内存分配空间
熟看了java编译原理等多本大神级别书籍后,小明信心满满的去面试字节跳动了,跳动的面试官说:小伙,来给我讲一下是不是所有的对象和数组都会在堆内存分配空间?
哲洛不闹
2019/08/20
7770
面试官:是不是所有的对象和数组都会在堆内存分配空间
Jvm创建对象之内存分配-JVM(七)
上篇文章介绍了jvm创建,会校验是否已加载类,没有则加载,通过之前学的源码,classLoader加载完之后,虚拟机开始给类分配内存,指针移动分配和free链表分配,解决并发分配情况用cap和TLAB方法。之后设置对象头部信息,有mark word线程锁,分代年龄等,klass pointer。还有指针压缩的概念。
用户9919783
2023/09/05
1930
Jvm创建对象之内存分配-JVM(七)
再清楚不过了,JVM逃逸分析,你一定得知道
提到JVM,相信大家一定知道JVM是什么?但是,提到逃逸分析,相信大多数人都可能一脸懵逼,逃逸分析到底是什么呢?接下来给大家分享一下。
Java程序猿阿谷
2020/12/16
2.3K0
再清楚不过了,JVM逃逸分析,你一定得知道
4.2 synchronized补充
对于一个方法而言, 里面加了三把锁, 这样是没有任何意义的. 所以可以将其进行粗化处理
用户7798898
2020/09/27
3500
5. java 对象是如何创建的?new背后到底做了什么
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。 new指令对应到语言层面上讲是,new关键词、对象克隆、对象序列化等。
源码之路
2021/03/02
8090
5. java 对象是如何创建的?new背后到底做了什么
JVM内存与垃圾回收篇第8章堆
为新对象分配内存是一件非常严谨和复杂的任务,JVM的设计者们不仅需要考虑内存如何分配、在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关,所以还需要考虑GC执行完内存回收后是否会在内存空间中产生内存碎片。
yuanshuai
2022/08/22
4540
JVM内存与垃圾回收篇第8章堆
栈分配与TLAB
在学习Java的过程中,一般认为new出来的对象都是被分配在堆上的,其实这个结论不完全正确,因为是大部分new出来的对象被分配在堆上,而不是全部。通过对Java对象分配的过程分析,可以知道有另外两个地方也是可以存放对象的。这两个地方分别栈 (涉及逃逸分析相关知识)和TLAB(Thread Local Allocation Buffer)。我们首先对这两者进行介绍,而后对Java对象分配过程进行介绍。
干货满满张哈希
2021/04/12
5910
JVM学习第一天(虚拟机的前世今生与与Java的内存区域)
其实说JVM的时候有很多人会懵, 也很不理解,我会写Java代码就可以了,我干嘛要学这个,其实不是的,学习JVM是很有必要性的;
彼岸舞
2020/09/30
5270
JVM简介—1.Java内存区域
Java虚拟机在执行Java程序的过程中,会把它所管理的内存划分为若干个不同的数据区域,这些区域各有各的用途以及各自的创建和销毁时间也不一样。有的区域会随着虚拟机的进程启动而存在,有的区域则依赖用户线程的启动和结束而进而跟着建立和销毁。
东阳马生架构
2025/03/10
390
JVM栈上分配对象内存与逃逸分析原理分析(Escape Analysis)
JVM中较前沿的优化技术,它与类型继承关系分析一样,并非直接优化代码,而是为其他优化措施提供依据的分析技术。
JavaEdge
2022/11/30
2730
JVM内存逃逸与栈上分配,程序员必须掌握的知识
下图中,可以看到直接将User对象返回出去,这样这个User对象有可能会被其他地方所改变,这样他的作用域就不只是在方法内部了,这样就是逃逸到方法外部了
黎明大大
2020/09/08
1.7K0
JVM内存逃逸与栈上分配,程序员必须掌握的知识
Java 引用逃逸那些事
为了对线上程序的性能进行优化分析, 最近在看广受推荐的《深入理解Java虚拟机》,整本书的内容不少, 目前只是根据自己所需的进行阅读, 在后续读完整本内容配合笔记再写篇博客来记录下.而现在阅读过程中,发现 引用逃逸 和 逃逸分析这个两个概念 并不太了解,还容易混淆,于是就写下这篇博客来帮助下认识 Java 中的 引用逃逸 和 逃逸分析.
闻人的技术博客
2019/09/19
1.7K0
Java 引用逃逸那些事
Battle:你会TLAB,我会逃逸分析
user对象在方法内部声明,且在内部置为null,未被方法外的方法所引用,我们就说user对象没有发生逃逸。
阿Q说代码
2021/07/20
3040
推荐阅读
相关推荐
Java虚拟机内存结构(JVM)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文