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

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

    推荐阅读
  • 耳机为何经常只坏一边(耳机为什么总是坏掉一只)

    接下来我们就一起去研究一下吧!耳机为何经常只坏一边这是因为平时使用习惯不好,制造耳机时两边耳机所使用的材料工艺都是一样的。不要过多的插拔插头,因为插头过度磨损也会影响音质。耳机使用完以后不可以“卷”在随身听上,应放入耳机配套的盒子或是袋子里。最好放点干燥剂,防止橡胶老化变形。使用耳机时候,不要用力拽拉耳机线,耳机线很细,容易出现断裂现象。

  • 梦见包被偷了什么预兆(梦见包被偷了有哪些预兆)

    下面希望有你要的答案,我们一起来看看吧!梦见包被偷了什么预兆梦见包被偷了什么预兆,得此梦五行主金,乃财运收益颇多之迹象,得他人之帮扶事业发展可有提升,秋天梦之吉利,夏天梦之不吉利。单身女人梦见包被偷了,主近期事业之拓展多与他人合作相关,则不可有一意孤行之想法,致灾祸不断。

  • 汽车票往返特惠怎么使用(车票用完可别扔)

    代用票、列车补票、到站补票、非实名制车票等暂不参与乘车积分累积。A“积分补登”操作一定要在车票载明的开车日期后90日内(含当日)。A在12306官网和手机app上,只能补登登录人本人的积分。想要帮老妈补登,一定要用她本人注册的12306账户补登哈,前提是妈妈也注册了常旅客会员。A申请人本人持有效身份证件原件,到会员服务窗口申请办理,完成身份认证,即可成为会员。

  • 鮸鱼做法大全(怎么做鲵鱼)

    以下内容希望对你有帮助!鮸鱼做法大全已经处理好的鮸鱼,只要稍稍冲洗就可以。捞出沥干水分,备用。坐油锅,加热,下姜丝与葱段,爆出香味。将鮸鱼块逐块码在姜葱上煎。再均匀撒上白糖。这之前不能翻面,易粘锅,易弄碎。淋上生抽,加入料酒稍煮一会。可以适当添点水。力度要轻些,易碎。稍销收汤汁,最后加入剩余葱段。逐块夹入碟,汤汁淋在鱼块上,姜丝与葱段码在两边。

  • 空气炸锅牛肉干(空气炸锅牛肉干的做法)

    放入冰箱,浸泡一晚。由于要过夜,所以调料不能太咸,以平时吃菜的咸度为标准,略低。

  • 三年级优秀作文(小学三年级优秀作文示例)

    三年级优秀作文当沙尘和雾霾笼罩着北京的上空时,当H7N9禽流感全国蔓延时,我乘坐时光穿梭机来到了十年后的北京。一幢幢拔地而起的高楼在清新的空气里显得格外艳丽。只见它一张嘴,嘴里吐出一团团黄色的颗粒,小鱼们都围拢来抢食。走上天桥,那里被一片绿荫笼罩,原来桥上种植了绿萝,葡萄等藤蔓植物,绿叶遮挡了阳光,给人们带来清凉。十年后的北京真的不一样了,它变得更美,更干净,更环保了,北京成为了人们生活的天堂。

  • 包子馅拌的稀了怎么办 包子馅拌的稀了怎么办呢

    做法:1、将小白菜清洗干净,去掉老叶,在锅中焯水一分钟后捞出过凉水。

  • 清朝灭亡后那些贵族去了哪里(清朝灭亡后皇族都去哪了)

    一步迟步步迟,后面清朝的国力相对西方资本主义国家来说,就开始慢慢落后。政权如此腐败,国内的仁人志士自然不想看着国家如此堕落,人民成为亡国奴。在1911年,发生了辛亥革命,推翻了清王朝的封建统治,大清皇帝在1912年的时候,在南京宣布退位,清朝自此成为史书上的文字。溥仪宣布退位之后,他几经波折,又跑到东北去当傀儡皇帝了,虽然屈辱,但是勉强活着。

  • 妹抖龙什么梗(妹抖龙什么意思)

    下面更多详细答案一起来看看吧!妹抖龙什么梗动画里的龙托尔虽然有满满的女王属性,但在面对女主小林的时候,却是实实在在的抖M,他吃自己的尾巴所以被称之为妹抖龙。动画改编自クール教信者的同名漫画作品。讲述了本是一个女性系统工程师的小林,在某次醉酒后跑到一座山上遇到了一头龙,听说其无家可归后半开玩笑地邀其到自己家,然后就开始了一段龙女仆与OL的故事。

  • 风花雪月的诗句(和风花雪月有关的诗句有什么)

    暮雨不来春不去,花满地月朦胧——贺铸《江城子》,接下来我们就来聊聊关于风花雪月的诗句?——贺铸《江城子》随风潜入夜,润物细无声。——杜甫《春望》花间一壶酒,独酌无相亲。——李白《月下独酌》千里黄云白日曛,北风吹雁雪纷纷。——杜甫《春夜喜雨》黑云压城城欲摧,甲光向日金鳞开。——李贺《雁门太守行》微雨池塘见,好风襟袖知。——杜牧《秋思》黄四娘家花满蹊,千朵万朵压枝低。