日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java 对象都是在堆上分配内存吗?

發(fā)布時(shí)間:2025/3/21 java 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 对象都是在堆上分配内存吗? 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

為了防止歧義,可以換個(gè)說法:Java對(duì)象實(shí)例和數(shù)組元素都是在堆上分配內(nèi)存的嗎?
答:不一定。滿足特定條件時(shí),它們可以在(虛擬機(jī))棧上分配內(nèi)存。

JVM內(nèi)存結(jié)構(gòu)很重要,多多復(fù)習(xí)

這和我們平時(shí)的理解可能有些不同。虛擬機(jī)棧一般是用來存儲(chǔ)基本數(shù)據(jù)類型、引用和返回地址的,怎么可以存儲(chǔ)實(shí)例數(shù)據(jù)了呢?這是因?yàn)镴ava JIT(just-in-time)編譯器進(jìn)行的兩項(xiàng)優(yōu)化,分別稱作逃逸分析(escape analysis)標(biāo)量替換(scalar replacement)。JIT是個(gè)復(fù)雜的話題,本文不贅述,看官如果想進(jìn)一步了解的話,可以參考這篇文章,它里面提供了幾篇有用的參考資料。

注意看一下JIT的位置

中文維基上對(duì)逃逸分析的描述基本準(zhǔn)確,摘錄如下:

在編譯程序優(yōu)化理論中,逃逸分析是一種確定指針動(dòng)態(tài)范圍的方法——分析在程序的哪些地方可以訪問到指針。當(dāng)一個(gè)變量(或?qū)ο?#xff09;在子程序中被分配時(shí),一個(gè)指向變量的指針可能逃逸到其它執(zhí)行線程中,或是返回到調(diào)用者子程序。
如果一個(gè)子程序分配一個(gè)對(duì)象并返回一個(gè)該對(duì)象的指針,該對(duì)象可能在程序中被訪問到的地方無法確定——這樣指針就成功“逃逸”了。如果指針存儲(chǔ)在全局變量或者其它數(shù)據(jù)結(jié)構(gòu)中,因?yàn)槿肿兞渴强梢栽诋?dāng)前子程序之外訪問的,此時(shí)指針也發(fā)生了逃逸。
逃逸分析確定某個(gè)指針可以存儲(chǔ)的所有地方,以及確定能否保證指針的生命周期只在當(dāng)前進(jìn)程或線程中。

簡單來講,JVM中的逃逸分析可以通過分析對(duì)象引用的使用范圍(即動(dòng)態(tài)作用域),來決定對(duì)象是否要在堆上分配內(nèi)存,也可以做一些其他方面的優(yōu)化。

以下的例子說明了一種對(duì)象逃逸的可能性。

??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范圍維持在方法內(nèi)部,未逃逸}

以JDK 1.8為例,可以通過設(shè)置JVM參數(shù)-XX:+DoEscapeAnalysis、-XX:-DoEscapeAnalysis來開啟或關(guān)閉逃逸分析(默認(rèn)當(dāng)然是開啟的)。下面先寫一個(gè)沒有對(duì)象逃逸的例子。

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;}} }

然后通過開啟和關(guān)閉DoEscapeAnalysis開關(guān)觀察不同。

  • 關(guān)閉逃逸分析

~?java?-XX:-DoEscapeAnalysis?EscapeAnalysisTest 76?ms ~?jmap?-histo?26031num?????#instances?????????#bytes??class?name ----------------------------------------------1:???????5000000??????120000000??me.lmagics.EscapeAnalysisTest$MyObject2:???????????636???????12026792??[I3:??????????3097????????1524856??[B4:??????????5088?????????759960??[C5:??????????3067??????????73608??java.lang.String6:???????????623??????????71016??java.lang.Class7:???????????727??????????43248??[Ljava.lang.Object;8:???????????532??????????17024??java.io.File9:???????????225??????????14400??java.net.URL10:???????????334??????????13360??java.lang.ref.Finalizer #?......
  • 開啟逃逸分析

~?java?-XX:+DoEscapeAnalysis?EscapeAnalysisTest 4?ms ~?jmap?-histo?26655num?????#instances?????????#bytes??class?name ----------------------------------------------1:???????????592???????11273384??[I2:?????????90871????????2180904??me.lmagics.EscapeAnalysisTest$MyObject3:??????????3097????????1524856??[B4:??????????5088?????????759952??[C5:??????????3067??????????73608??java.lang.String6:???????????623??????????71016??java.lang.Class7:???????????727??????????43248??[Ljava.lang.Object;8:???????????532??????????17024??java.io.File9:???????????225??????????14400??java.net.URL10:???????????334??????????13360??java.lang.ref.Finalizer #?......

可見,關(guān)閉逃逸分析之后,堆上有5000000個(gè)MyObject實(shí)例,而開啟逃逸分析之后,就只剩下90871個(gè)實(shí)例了,不管是實(shí)例數(shù)還是內(nèi)存占用都只有原來的2%不到。另外,如果把堆內(nèi)存限制得小一點(diǎn)(比如加上-Xms10m -Xmx10m),并且打印GC日志(-XX:+PrintGCDetails)的話,關(guān)閉逃逸分析還會(huì)造成頻繁的GC,開啟逃逸分析就沒有這種情況。這說明逃逸分析確實(shí)降低了堆內(nèi)存的壓力。

但是,逃逸分析只是棧上內(nèi)存分配的前提,接下來還需要進(jìn)行標(biāo)量替換才能真正實(shí)現(xiàn)。

所謂標(biāo)量,就是指JVM中無法再細(xì)分的數(shù)據(jù),比如int、long、reference等。相對(duì)地,能夠再細(xì)分的數(shù)據(jù)叫做聚合量。仍然考慮上面的例子,MyObject就是一個(gè)聚合量,因?yàn)樗蓛蓚€(gè)標(biāo)量a、b組成。通過逃逸分析,JVM會(huì)發(fā)現(xiàn)myObject沒有逃逸出allocate()方法的作用域,標(biāo)量替換過程就會(huì)將myObject直接拆解成a和b,也就是變成了:

??static?void?allocate()?{int?a?=?2019;double?b?=?2019.0;}

可見,對(duì)象的分配完全被消滅了,而int、double都是基本數(shù)據(jù)類型,直接在棧上分配就可以了。所以,在對(duì)象不逃逸出作用域并且能夠分解為純標(biāo)量表示時(shí),對(duì)象就可以在棧上分配。

JVM提供了參數(shù)-XX:+EliminateAllocations來開啟標(biāo)量替換,默認(rèn)仍然是開啟的。顯然,如果把它關(guān)掉的話,就相當(dāng)于禁止了棧上內(nèi)存分配,只有逃逸分析是無法發(fā)揮作用的。在Debug版JVM中,還可以通過參數(shù)-XX:+PrintEliminateAllocations來查看標(biāo)量替換的具體情況。

除了標(biāo)量替換之外,通過逃逸分析還能實(shí)現(xiàn)同步消除(synchronization elision),當(dāng)然它與本文的主題無關(guān)了。舉個(gè)例子:

??private?void?someMethod()?{Object?lockObject?=?new?Object();synchronized?(lockObject)?{System.out.println(lockObject.hashCode());}}

lockObject這個(gè)鎖對(duì)象的生命期只在someMethod()方法中,并不存在多線程訪問的問題,所以synchronized塊并無意義,會(huì)被優(yōu)化掉:

??private?void?someMethod()?{Object?lockObject?=?new?Object();System.out.println(lockObject.hashCode());}

累了,晚安晚安。

總結(jié)

以上是生活随笔為你收集整理的Java 对象都是在堆上分配内存吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。