Java中的对象都是在堆上分配的吗?
作者:LittleMagic
https://www.jianshu.com/p/8377e09971b8
为了防止歧义,可以换个说法:
Java对象实例和数组元素都是在堆上分配内存的吗?
答:不一定。满足特定条件时,它们可以在(虚拟机)栈上分配内存。
JVM内存结构很重要,多多复习
这和我们平时的理解可能有些不同。虚拟机栈一般是用来存储基本数据类型、引用和返回地址的,怎么可以存储实例数据了呢?
这是因为Java JIT(just-in-time)编译器进行的两项优化,分别称作逃逸分析(escape analysis)和标量替换(scalar replacement)。
注意看一下JIT的位置
中文维基上对逃逸分析的描述基本准确,摘录如下:
在编译程序优化理论中,逃逸分析是一种确定指针动态范围的方法——分析在程序的哪些地方可以访问到指针。当一个变量(或对象)在子程序中被分配时,一个指向变量的指针可能逃逸到其它执行线程中,或是返回到调用者子程序。
如果一个子程序分配一个对象并返回一个该对象的指针,该对象可能在程序中被访问到的地方无法确定——这样指针就成功“逃逸”了。如果指针存储在全局变量或者其它数据结构中,因为全局变量是可以在当前子程序之外访问的,此时指针也发生了逃逸。
逃逸分析确定某个指针可以存储的所有地方,以及确定能否保证指针的生命周期只在当前进程或线程中。
简单来讲,JVM中的逃逸分析可以通过分析对象引用的使用范围(即动态作用域),来决定对象是否要在堆上分配内存,也可以做一些其他方面的优化。
关于逃逸分析,以下的例子说明了一种对象逃逸的可能性。
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来开启或关闭逃逸分析(默认当然是开启的)。
下面先写一个没有对象逃逸的例子。
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开关观察不同。
关闭逃逸分析
~ java -XX:-DoEscapeAnalysis EscapeAnalysisTest
76 ms
~ jmap -histo 26031num #instances #bytes class name
----------------------------------------------1: 5000000 120000000 me.lmagics.EscapeAnalysisTest$MyObject2: 636 12026792 [I3: 3097 1524856 [B4: 5088 759960 [C5: 3067 73608 java.lang.String6: 623 71016 java.lang.Class7: 727 43248 [Ljava.lang.Object;8: 532 17024 java.io.File9: 225 14400 java.net.URL10: 334 13360 java.lang.ref.Finalizer
# ......
开启逃逸分析
~ java -XX:+DoEscapeAnalysis EscapeAnalysisTest
4 ms
~ jmap -histo 26655num #instances #bytes class name
----------------------------------------------1: 592 11273384 [I2: 90871 2180904 me.lmagics.EscapeAnalysisTest$MyObject3: 3097 1524856 [B4: 5088 759952 [C5: 3067 73608 java.lang.String6: 623 71016 java.lang.Class7: 727 43248 [Ljava.lang.Object;8: 532 17024 java.io.File9: 225 14400 java.net.URL10: 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,也就是变成了:
static void allocate() {int a = 2019;double b = 2019.0;
}
可见,对象的分配完全被消灭了,而int、double都是基本数据类型,直接在栈上分配就可以了。所以,在对象不逃逸出作用域并且能够分解为纯标量表示时,对象就可以在栈上分配。
JVM提供了参数-XX:+EliminateAllocations来开启标量替换,默认仍然是开启的。显然,如果把它关掉的话,就相当于禁止了栈上内存分配,只有逃逸分析是无法发挥作用的。
在Debug版JVM中,还可以通过参数-XX:+PrintEliminateAllocations来查看标量替换的具体情况。
除了标量替换之外,通过逃逸分析还能实现同步消除
(synchronization elision),当然它与本文的主题无关了。
举个例子:
private void someMethod() {Object lockObject = new Object();synchronized (lockObject) {System.out.println(lockObject.hashCode());}
}
lockObject这个锁对象的生命期只在someMethod()方法中,并不存在多线程访问的问题,所以synchronized块并无意义,会被优化掉:
private void someMethod() {Object lockObject = new Object();System.out.println(lockObject.hashCode());
}
猜你喜欢
1、GitHub 标星 3.2w!史上最全技术人员面试手册!FackBoo发起和总结
2、如何才能成为优秀的架构师?
3、从零开始搭建创业公司后台技术栈
4、程序员一般可以从什么平台接私活?
5、37岁程序员被裁,120天没找到工作,无奈去小公司,结果懵了...
6、滴滴业务中台构建实践,首次曝光
7、不认命,从10年流水线工人,到谷歌上班的程序媛,一位湖南妹子的励志故事
8、15张图看懂瞎忙和高效的区别!
Java中的对象都是在堆上分配的吗?相关推荐
- Java对象都是在堆上分配空间吗?答案竟然是...
作者 l Hollis 来源 l Hollis(ID:hollischuang) Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点,所以,即使是一个Java的初学者,也一定或 ...
- Java 对象都是在堆上分配内存吗?
为了防止歧义,可以换个说法:Java对象实例和数组元素都是在堆上分配内存的吗? 答:不一定.满足特定条件时,它们可以在(虚拟机)栈上分配内存. JVM内存结构很重要,多多复习 这和我们平时的理解可能有 ...
- java 堆_Java 对象都是在堆上分配内存吗?
为了防止歧义,可以换个说法:Java对象实例和数组元素都是在堆上分配内存的吗? 答:不一定.满足特定条件时,它们可以在(虚拟机)栈上分配内存. JVM内存结构很重要,多多复习 这和我们平时的理解可能有 ...
- 【性能优化】面试官:Java中的对象和数组都是在堆上分配的吗?
写在前面 从开始学习Java的时候,我们就接触了这样一种观点:Java中的对象是在堆上创建的,对象的引用是放在栈里的,那这个观点就真的是正确的吗?如果是正确的,那么,面试官为啥会问:"Jav ...
- python中一切皆是对象,对象都是在堆上存放的,一切都是指针
1 由于对象都是在堆上存放的,所以,返回值可以任意返回. 这样看来,闭包里面的外部函数的内部变量也是对象,所以,当返回的内部函数被调用时,这个外部函数的变量就没有被释放. 这样看来,返回时,不需要考虑 ...
- java标量替换_JAVA逃逸分析、栈上分配、标量替换、同步消除
一.逃逸分析 逃逸分析是编译语言中的一种优化分析,而不是一种优化的手段.通过对象的作用范围的分析,为其他优化手段提供分析数据从而进行优化. 逃逸分析包括: 全局变量赋值逃逸 方法返回值逃逸 实例引用发 ...
- 求你了,别再说Java对象都是在堆内存上分配空间的了!
Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点,所以,即使是一个Java的初学者,也一定或多或少的对JVM有一些了解.可以说,关于JVM的相关知识,基本是每个Java开发者 ...
- 对象并不一定都是在堆上分配内存的
JVM内存分配策略 关于JVM的内存结构及内存分配方式,不是本文的重点,这里只做简单回顾.以下是我们知道的一些常识: 1.根据Java虚拟机规范,Java虚拟机所管理的内存包括方法区.虚拟机栈.本地方 ...
- Java中,一切皆是对象——java中的对象类型与基本数据类型的区别
起因:取一个list给另一个list赋值,然后在另一个list中取出进行修改.list其中类型为对象时,String时,基本数据类型时. [java中的对象类型与基本数据类型的区别] #声明和实例化 ...
- java构造方法的签名_如何在 Java 中构造对象(学习 Java 编程语言 034)
1. 构造器 Java 对象都是在堆中构造的. 先看看 Employee 类的构造器: public class Employee { private String name; private dou ...
最新文章
- Mac中使用brew安装指定版本软件包
- IDEA中MAVEN项目打JAR包的简单方法
- 微软云创益大赛获奖团队风采:做一个中国特色的.Net源代码社区
- Machine Learning week 9 quiz: programming assignment-Anomaly Detection and Recommender Systems
- 《淘宝网开店 进货 运营 管理 客服 实战200招》——2.3 开店需要掌握哪些 进货技巧...
- 科大星云诗社动态20210415
- mysql 优化设计库_数据库优化设计与SQL优化
- jsp开发环境的搭建
- 调用restful接口_SSM实现RESTFul风格的CURD操作
- mailto 附带附件_为什么附带项目如此重要
- HDU3507-Print Article-斜率dp入门题
- 老程序员也搞不定的 4 个巨坑!
- Openlayer:学习笔记之解析地图组成
- DreamWeaver CS3中的SPRY的自定义验证
- Ubuntu安装网易云音乐
- C语言函数之 fabs()
- nacos 未读取到合法数据,请检查导入的数据文件
- vm虚拟机安装教程win7
- 一个10年C++程序员对技术和业务的感悟,献给还在迷茫中的你
- 能独步天下吗?揭开至强E5处理器的最后面纱
热门文章
- samba 部署和优化
- http://www.oreilly.com/catalog/errataunconfirmed.csp?isbn=9780596529321
- 【转】为什么要用GIT而不是SVN
- WPF控件TreeView使用
- 雅虎宣布支持谷歌OpenSocial标准
- 磁盘已满,如何从 Mac 中删除大文件?
- TG Pro for Mac(Mac电脑硬件温度监测工具)
- Spark SQL 用户自定义函数UDF、用户自定义聚合函数UDAF 教程(Java踩坑教学版)
- 《机器人自动化:建模、仿真与控制》——第2章 仿真
- JAVA常见算法题(十九)