Java中的对象都是在堆上分配的吗?
2020-05-20 09:12:52来源:博客园 阅读 ()
Java中的对象都是在堆上分配的吗?
作者:LittleMagic https://www.jianshu.com/p/8377e09971b8
为了防止歧义,可以换个说法:
Java对象实例和数组元素都是在堆上分配内存的吗?
答:不一定。满足特定条件时,它们可以在(虚拟机)栈上分配内存。
JVM内存结构很重要,多多复习
这和我们平时的理解可能有些不同。虚拟机栈一般是用来存储基本数据类型、引用和返回地址的,怎么可以存储实例数据了呢?
这是因为Java JIT(just-in-time)编译器进行的两项优化,分别称作逃逸分析(escape analysis)和标量替换(scalar replacement)。
注意看一下JIT的位置
中文维基上对逃逸分析的描述基本准确,摘录如下:
在编译程序优化理论中,逃逸分析是一种确定指针动态范围的方法——分析在程序的哪些地方可以访问到指针。当一个变量(或对象)在子程序中被分配时,一个指向变量的指针可能逃逸到其它执行线程中,或是返回到调用者子程序。
如果一个子程序分配一个对象并返回一个该对象的指针,该对象可能在程序中被访问到的地方无法确定——这样指针就成功“逃逸”了。如果指针存储在全局变量或者其它数据结构中,因为全局变量是可以在当前子程序之外访问的,此时指针也发生了逃逸。
逃逸分析确定某个指针可以存储的所有地方,以及确定能否保证指针的生命周期只在当前进程或线程中。
简单来讲,JVM中的逃逸分析可以通过分析对象引用的使用范围(即动态作用域),来决定对象是否要在堆上分配内存,也可以做一些其他方面的优化。
关于逃逸分析,大家可以看下这篇文章:面试问我 Java 逃逸分析,瞬间被秒杀了。以下的例子说明了一种对象逃逸的可能性。
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?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
# ......
开启逃逸分析
~ 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,也就是变成了:
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.Java JVM、集合、多线程、新特性系列教程
2.Spring MVC、Spring Boot、Spring Cloud 系列教程
3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程
4.Java、后端、架构、阿里巴巴等大厂最新面试题
觉得不错,别忘了点赞+转发哦!
原文链接:https://www.cnblogs.com/javastack/p/12923778.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:简单了解 HTTP 协议
下一篇:我去,为什么最左前缀原则失效了?
- 国外程序员整理的Java资源大全(全部是干货) 2020-06-12
- 2020年深圳中国平安各部门Java中级面试真题合集(附答案) 2020-06-11
- 2020年java就业前景 2020-06-11
- 04.Java基础语法 2020-06-11
- Java--反射(框架设计的灵魂)案例 2020-06-11
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash