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

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

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

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



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

    推荐阅读
  • 朱迅三次患癌 靠坐轮椅出行(坚强的朱迅17岁异国患癌)

    可当她打电话把这个好消息告诉母亲的时候,被泼了盆冷水。这一查被医生告知得了血管瘤,必须开刀动手术,这对还未成年的朱迅来说无异于晴天霹雳。不久之后,血管瘤复发,朱迅不得不第二次躺在了手术台上。但母亲却成了朱迅心里一直的隐痛,很长时间没法与自己和解。就是在这种条件下,她依然凭着自己的努力,考上了日本亚细亚大学,并取得了很好的成绩。

  • 星露谷物语全菜谱图鉴及获取途径(星露谷物语全菜谱图鉴及获取途径)

    下面更多详细答案一起来看看吧!星露谷物语全菜谱图鉴及获取途径菜谱NPC赠送和电视频道是获取菜谱的主要途径。电视频道-酱料女皇会在每周日发布一个新菜谱,每周三会从已发布的菜谱中随机选取一个进行重播,2年为一个周期,第3年会从头开始,依此循环。这意味着如果错过一个菜谱就要再等2年,当然原则上是等2年,每周三你还有一次机会补救,重播的菜谱会不会刚好就是你缺少的那个要看运气了。

  • dota2 当前版本1号位推荐(DOTA27.32新版本改动英雄篇)

    对1200范围内所有非隐身的敌方英雄施加一个诅咒,使他们在战争迷雾中显形,并且根据250范围内树木的数量造成持续伤害。在此情况下树人也被视为树木。兽王开始敲击他的战鼓,而且速度不断加快。兽王被眩晕或沉默时战鼓不会造成伤害,也不会具有回复效果。冥界亚龙冲向地面,对500范围内敌人造成4秒缴械,并且1200范围内所有敌人均被溅上腐蚀皮肤的酸液。

  • 好吃米饭做法(这样做拿肉都不换)

    好吃米饭做法酱香肉末土豆炒饭食材准备:米饭一碗、土豆一个、猪肉馅50克、葱花适量、小米辣2个、线椒一根、生抽2勺、蚝油1茶匙、豆瓣酱一大勺、清水适量、食用油少许。锅中加入适量清水,将其煮沸后下入切好的土豆丁,煮熟后捞出。接下来,在炒锅内倒入少许食用油,油热后放入葱花以及切好的小米辣和线椒,用铲子翻炒出香味。之后,向锅中倒入调味汁。然后,取一大勺豆瓣酱放入锅中,继续用锅铲将全部食材翻炒均匀。

  • 如何去放下一个暗恋的人(如何放下一个暗恋多年的人)

    故事的情节大都烂得掉渣,无非是女孩喜欢上了男孩,然后开始上演一系列的内心戏。类似的猜测和纠结,每天都会在心中的上演。诸如此类,一个人的兵荒马乱,一个人自导自演的独角戏,对方却全然不知。如果这两者你都不想选,那你必须承受内心的煎熬和痛苦,以及负面情绪对生活和工作的影响。工作如此,爱情更是如此。勇敢的面对,勇敢的放手,这才是对待感情最理智的态度。END.愈姑娘:自由撰稿人,一支笔写尽人生百态,欢迎关注我。

  • 渭南冬季旅游景点大全(五一假期陕西周边游推荐地)

    五一假期陕西周边游推荐地五一小长假陕西周边游推荐地渭南桃花源民俗文化园(国家3A级景区)西安自驾渭南桃花源,1小时即可到达,高速还免费渭南桃花源身为抖友的肯定、网红打卡!蜂拥而至!福利可别都让他们抢光了!抓紧来看渭南特色美食。

  • 曹操是最强英雄(69秒点铁成金曹操竟是改造文章的祖师爷)

    提到“东临碣石以观沧海”,世人都晓得是出自曹操的名篇《观沧海》,然而你知道么,这句诗竟然是曹孟德改嫁的,原文出自司马相如。在近日播出的《国学小名士》中,嘉宾李山和郦波就对此进行了解读。原来不止《观沧海》,曹操许多的诗篇都是如此,譬如“青青子衿,悠悠我心,但为君故,沉吟至今”,李山就赞誉曹操有“点铁成金”的才能。

  • 引起宝宝长胎记的原因都有什么呢(听说胎记是赐予宝宝最好的礼物)

    胎记在医学上被称为“母斑”或“痣”,当皮肤组织在发育时发生异常的增生,就会在皮肤表面出现胎记。其实只有直径大于20厘米的黑素细胞痣才可能产生癌变,父母不用过于焦虑。

  • 白贝怎么保存(白贝的制作方法)

    接下来我们就一起去研究一下吧!白贝怎么保存海鲜最好吃应季的,这样最新鲜健康。白贝的保存方法是除去肉洗干净,晒干了保存。还有开了口的尽量不吃,那样的不太新鲜。建议购买白贝时量不要太多,以免放久了不新鲜。这道汤对于调理肠胃、提高免疫力、养胃健脾有一定的作用。首先先用热水将白贝焯一下,煮上20分钟,再加入白萝卜丝、姜丝煮上5分钟。开锅后放上些葱花,加入几滴香油、盐调味即可。