科百科
当前位置: 首页 范文大全

java对象存在堆里(Java中的对象都是在堆上分配的吗)

时间:2023-05-25 作者: 小编 阅读量: 1 栏目名: 范文大全

当一个变量(或对象)在子程序中被分配时,一个指向变量的指针可能逃逸到其它执行线程中,或是返回到调用者子程序。逃逸分析确定某个指针可以存储的所有地方,以及确定能否保证指针的生命周期只在当前进程或线程中。以下的例子说明了一种对象逃逸的可能性。这说明逃逸分析确实降低了堆内存的压力。所以,在对象不逃逸出作用域并且能够分解为纯标量表示时,对象就可以在栈上分配。



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());

小编分类整理了许多java进阶学习材料和BAT面试题,需要资料的请私信小编, 就能领取2019年java进阶学习资料和BAT面试题以及《Effective Java》(第3版)电子版书籍。

    推荐阅读
  • 摘抄的格式怎么写(摘抄的格式怎么写最好正确点)

    可按原书或原文系统摘录;也可摘录重要论点和段落;还可摘录重要数字。将读书体会、感想、收获写出来;综合观点、见解,提出自己看法并记录下来,也是很好的读书方法。

  • 西宁绿地中式装修效果图(西宁市南门大世界-超实用的现代简约风特点-造价低实用美观)

    投稿设计师:宴军小区:西宁市南门大世界120㎡装修设计单位:西宁业之峰装饰有限公司装修风格:现代简约效果图展示如果您需要全套图纸,效果图,施工图,报价,请点击广告

  • 小学数学公式大全整理表(小学数学大汇总)

    学会一元一次方程式的例法及计算。即例出代有χ的算式并计算。把百分数化成小数,只要把百分号去掉,同时把小数点向左移动两位。其中最大的一个,叫做最大公约数。

  • 怎样推导绝对星等与光度的关系(光度和绝对星等的关系是什么)

    怎样推导绝对星等与光度的关系绝对星等指的是恒星在距离d=10秒差距时候的视星等,视星等和恒星的亮度直接相关,注意是亮度,指的是人的眼睛接受到恒星光的强度。首先通过光度L计算出恒星在绝对星等要求的d处的亮度E,就是E=L/,然后通过亮度E和视星等m的关系,普森公式m=-2.5lgE+a这里求出的视星等就是绝对星等,因为E是用距离d10秒差距算出的。

  • 乔治王子今年多大(乔治王子9岁生日排面大)

    7月22号是乔治王子9岁的生日,这一天威廉王子跟凯特王妃也是高调的在社交媒体上发布了一张全新的照片,为儿子庆生,而且这一张近身照,还登上了各大杂志的头条,非常有排面。凯特高调晒长子帅照为他庆生!POLO衫是很有代表性的休闲风单品,除了与牛仔裤组合外,像乔治一样与运动短裤进行搭配也是很好的选择。穿POLO衫笑出大白牙霸占头条,帅过威廉声明:文字原创,图片来自网络,如有侵权,联系删除。

  • 杨三俊名字打分100分 男孩名字杨俊什么?

    文章目录:一、杨三俊相关名字打分116二、杨三俊相关名字评分113三、杨三俊相关名字推荐四、杨三俊相关名字大全五、其他人还看了一、杨三俊相关名字打分116杨清仿杨增新杨敏之杨展明杨云庆杨美珍杨新顺杨熊杨怀山杨仁忠杨镇江杨西宁杨达杨庆海杨宗平杨剑涛杨和荣杨伟伟杨灿杨奶果杨综伟杨国建杨宇辉杨农杨叉杨晓彤杨俊卿杨冠华杨蟠杨炳坤杨毅强杨冰玉杨誉杨丽娟杨培杨二杨东升杨海川杨承杨一新杨世国杨奇煜杨鸡肉杨旭峰杨

  • 致老师的一封信(致老师的一封信简述)

    树木的成长,离不开阳光雨露,只有他们无私的施舍,才会有生机勃勃。花朵的生活,离不开大地,有了大地提供的营养,才会有花儿的艳丽动人。老师,太谢谢您了!老师,谢谢您对我们认真负责的教育,谢谢您对我们的关心,谢谢您教给了我们许多知识,谢谢您对我们无私的奉献,一千声一万声“谢谢”都无法回报您对我们的教导。

  • 母猫会发出咕噜咕噜声吗(幼猫会用咕噜声表达一些情绪)

    幼猫咕噜咕噜响是舒服、愉悦、高兴的表现,代表猫觉得所处的环境很安全、很放心;还有一种可能就是幼猫想要主人陪着玩,发出咕噜声来吸引主人的注意。这一行为是先天性的,刚刚出生不久的幼猫,就已学会用咕噜咕噜的声音表达一些情绪了。对于幼猫来说,咕噜咕噜的声音属于肢体语言的一种,咕噜的振动声是猫科动物特有的声音。当幼猫对主人发出咕噜咕噜的响声时,主人不要打断幼猫,也不要在此时将猫赶走,让猫失落离开。

  • 有哪六个火箭发射基地(种子岛现状:世界最美丽的火箭发射基地)

    波照间岛是琉球群岛中八重山群岛的组成部分,位于石垣岛西南方56公里。如今种子岛宇宙中心,已经是日本最大的火箭和卫星发射基地,拥有三座发射场。种子岛的吉信发射场是目前世界上最大和最现代化的发射场之一。吉信发射场也是目前世界上最大和最现代化的发射场之一,可与欧洲航天发射中心的库鲁阿里安4发射场以及美国肯尼迪航天发射中心的卡纳维拉尔角大力神3发射场相媲美。

  • 王羲之竖笔字写法(王羲之书法偏旁写法大全)

    写好了偏旁,书法也就成了一半,偏旁没有固定的写法,不同的字组合方式就不一样。以下仅列“二王”书法中的部分典型部首,以供读者参考。