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

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版)电子版书籍。

    推荐阅读
  • 红掌从哪能看出要开花了(红掌好看毛病也多)

    最近很多花友都在问红掌的养护问题,红掌花色鲜艳,寓意吉祥,从年前到现在一直都比较热卖,非常受欢迎,所以今天花花就给大家详细介绍一下红掌!把磷钾肥、花多多2号水溶肥兑水灌根,一周一次就有效果。如果叶子中间总是出现一团干枯,可能是炭疽病引起的,用多菌灵喷叶灌根,一周一次,连续三次会有效果。

  • 苹果xr网络太慢有什么办法解决(苹果xr上网速度慢)

    方法三:点击Wi-Fi后面的感叹号符号,配置DNS为8.8.8.8或者114.114.114.114即可。

  • 双胞胎字的正确写法(这些双胞胎汉字不能看)

    前段时间一首《生僻字》歌曲火爆网络。歌词里包含70多个生僻字,成功难倒80%国人。网友们纷纷表示不认识,感觉白上九年义务教育了。己VS已一个是自己的“己”,一个是已经的“已”。区别在“友”的上头是一横,还是一个宝盖头。姬(jī)VS姫姬是古代对妇女的美称,而姫是谨慎的意思。祗(zhī)表示恭敬,祗候回音。最后国家语委“红牌”罚下了其中一个。只收录“胄”字形作为使用规范。别说外国友人看起来都要抓狂。

  • 思维导图简单画法(思维导图怎样画法)

    思维导图简单画法先把纸张横过来放,这样宽度比较大一些。在纸的中心,画出能够代表你心目中的主体形象的中心图像。再用水彩笔尽任意发挥你的思路。绘画时,应先从图形中心开始,画一些向四周放射出来的粗线条。每一条线都使用不同的颜色这些分枝代表关于你的主体的主要思想。在绘制思维导图的时候,你可以添加无数根线。同时,它更容易记忆。使用彩色水笔以及一点儿想象。

  • 韦悫的父亲(孙中山秘书韦悫与江淮大学)

    15岁,韦悫因参与刺杀清政府官员而遭两广总督张鸣岐通缉。1918年6月,韦悫获奥柏林学院文学学士学位。在美国留学期间,韦悫积极参与各种社会活动,曾被选为留美中国学生会中部分部会长和芝加哥中国学生会会长。1920年6月,韦悫与旅美华侨梅宗周的女儿梅美恩结婚。江淮大学前后共两期,约有120余名学生。由于战争形势及师资缺乏等原因,江淮大学于1944年6月正式解散。

  • kissgoodbye歌词(kissgoodbye歌简介)

    以下内容大家不妨参考一二希望能帮到您!

  • lol酒桶上单怎么玩(都来看看吧)

    lol酒桶上单怎么玩?接下来我们就一起去了解一下吧!lol酒桶上单怎么玩酒桶在对线的时候注意释放Q技能去限制对手的走位,以此磨低对手的血量,同时在对线的时候如果对手处于技能CD的真空期,酒桶可以利用E技能近身打出控制效果然后衔接WA能打出一定的输出,在中期的时候酒桶可以去游走,利用R技能先手炸开阵型然后再接E近身输出,打团的时候可以进场控对手脆皮配合队友收割。

  • 6大凶手会致使男性冷淡

    1、红参肉苁蓉需要20克的红参,一对蛤蜊,50克肉苁蓉全部放在一升的米酒里面,一个周之后就可以饮用了,非常适合于男性的性冷淡。

  • 民办本科男寝(双满月的上海大学生咋样了)

    获胜者将代表中国出征国际公众演讲比赛,与来自全球50多个国家和地区选手同台竞技。疫情期封控在寝室,她却成功晋级2022年国际公众演讲比赛。5月14日凌晨,好消息传来,这个宅在寝室里参加比赛的非英语专业大学生,与来自全球30多个不同国家和地区选手同台竞技,拿到了IPSC全球总冠军。华东理工大学从3月中旬进行封控管理至今,理工生们的“宅寝”生活多姿多彩。

  • 柴油发动机动力不足故障原因和现象是什么?(了解一下)

    柴油发动机动力不足故障原因和现象是什么低压油路供油阻力大,造成供油压力不足。喷油泵故障,其故障原因包括:柱塞偶件磨损,造成泄露过多;出油阀密封不良、发卡,或其弹簧折断;柱塞弹簧失效;滚轮、凸轮磨损。喷油泵供油量调整不当,全负荷供油量不足。调速器因调整不当或高速弹簧过软引起的额定转速下降。喷油器喷孔积碳或调压弹簧的弹力调整不当。柴油发动机动力不足的现象是不易启动或启动后转速不易提高。