溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JVM翻越內(nèi)存管理的墻是什么

發(fā)布時間:2022-05-19 19:46:12 來源:億速云 閱讀:328 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“JVM翻越內(nèi)存管理的墻是什么”的相關(guān)知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“JVM翻越內(nèi)存管理的墻是什么”文章能幫助大家解決問題。

JVM運行時數(shù)據(jù)區(qū)域

Java虛擬機在執(zhí)行Java程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域,有的區(qū)域隨著虛擬機進(jìn)程的啟動而一直存在,有些區(qū)域則是依賴用戶線程的啟動和結(jié)束而建立和銷毀。

線程私有內(nèi)存:

由于JVM多線程是通過線程輪流切換、分配處理器執(zhí)行時間的方式來實現(xiàn)的,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內(nèi)核)都只會執(zhí)行一條線程中的指令。

因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個獨立的程序計數(shù)器,各條線程之間計數(shù)器互不影響,獨立存儲,我們稱這類內(nèi)存區(qū)域為“線程私有”的內(nèi)存。

JVM翻越內(nèi)存管理的墻是什么

程序計數(shù)器

程序計數(shù)器是一塊較小的內(nèi)存空間,它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。 它是程序控制流的指示器,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成。

字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令。

如果線程正在執(zhí)行的是一個Java方法,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址。

如果正在執(zhí)行的是本地(Native)方法,這個計數(shù)器值則應(yīng)為空。

Java虛擬機棧

Java虛擬機棧描述的是Java方法執(zhí)行的線程內(nèi)存模型,它也是線程私有內(nèi)存區(qū)域,生命周期和線程一樣。

JVM翻越內(nèi)存管理的墻是什么

棧楨

每個方法被執(zhí)行的時候,Java虛擬機都會同步創(chuàng)建一個棧幀用于存儲局部變量表、操作數(shù)棧、動態(tài)連接、方法出口等信息。每一個方法被調(diào)用直至執(zhí)行完畢的過程,就對應(yīng)著一個棧幀在虛擬機棧中從入棧到出棧的過程。

1.局部變量表

局部變量表存放了編譯期可知的:基本數(shù)據(jù)類型、對象引用、和returnAddress類型(指向了一條字節(jié)碼指令的地址)

局部變量表中的存儲空間以局部變量槽表示。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個方法時,這個方法需要在棧幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大?。ㄟ@里說的“大小”是指變量槽的數(shù)量,一個變量槽多大是由具體虛擬機實現(xiàn)的)

2.異常情況

1.StackOverflowError異常:線程請求的棧深度大于虛擬機所允許的深度

2.OutOfMemoryError異常:Java虛擬機棧容量可以動態(tài)擴展,當(dāng)棧擴展時無法申請到足夠的內(nèi)存。 在HotSpot虛擬機上是不會由于虛擬機棧無法擴展而導(dǎo)致OutOfMemoryError異常。只要線程申請??臻g成功了就不會有OOM,但是如果申請時就失敗,仍然是會出現(xiàn)OOM異常的。

本地方法棧

與虛擬機棧所發(fā)揮的作用是非常相似的。本地方法棧則是為虛擬機使用到的本地(Native)方法服務(wù)。

HotSpot虛擬機直接就把本地方法棧和虛擬機棧合二為一

Java堆

Java堆是虛擬機所管理的內(nèi)存中最大的一塊,被所有線程共享的一塊內(nèi)存區(qū)域。 Java堆是垃圾收集器管理的內(nèi)存區(qū)域。所以也經(jīng)常被稱為GC堆

Java堆會在虛擬機啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實例,Java世界里“幾乎”所有的對象實例都在這里分配內(nèi)存。

JVM翻越內(nèi)存管理的墻是什么

從回收內(nèi)存的角度看,由于現(xiàn)代垃圾收集器大部分都是基于分代收集理論設(shè)計的,所以Java堆中經(jīng)常會出現(xiàn)“新生代”“老年代”“永久代”“Eden空間”“From Survivor空 間”“To Survivor空間”等名詞。

在之前(以G1收集器的出現(xiàn)為分界),作為業(yè)界絕對主流的HotSpot虛擬機,它內(nèi)部的垃圾收集器全部都基于“經(jīng)典分代” 來設(shè)計,需要新生代、老年代收集器搭配才能工作,在這種背景下,上述說法還算是不會產(chǎn)生太大歧義。但是到了今天,垃圾收集器技術(shù)與十年前已不可同日而語,HotSpot里面也出現(xiàn)了不采用分代設(shè)計的新垃圾收集器,再按照上面的提法就有很多需要商榷的地方了。

分配緩沖區(qū)TLAB(Thread Local Allocation Buffer)

如果從分配內(nèi)存的角度看,所有線程共享的Java堆中可以劃分出多個線程私有的分配緩沖區(qū) (Thread Local Allocation Buffer,TLAB)。

無論如何劃分,都不會改變Java堆中存儲內(nèi)容的共性,無論是哪個區(qū)域,存儲的都只能是對象的實例,將Java堆細(xì)分的目的只是為了更好地回收內(nèi)存,或者更快地分配內(nèi)存。

Java堆的大小設(shè)定

Java堆既可以被實現(xiàn)成固定大小的,也可以是可擴展的,不過當(dāng)前主流的Java虛擬機都是按照可擴展來實現(xiàn)的(通過參數(shù)-Xmx-Xms設(shè)定)。如果在Java堆中沒有內(nèi)存完成實例分配,并且堆也無法再擴展時,Java虛擬機將會拋出OutOfMemoryError異常。

方法區(qū)

方法區(qū)別名叫作“非堆”。它用于存儲已被虛擬機加載的類型信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼緩存等數(shù)據(jù)。是各個線程共享的內(nèi)存區(qū)域。

JVM翻越內(nèi)存管理的墻是什么

很多人都更愿意把方法區(qū)稱呼為“永久代”(PermanentGeneration),或?qū)烧呋鞛橐徽劇1举|(zhì)上這兩者并不是等價的。因為僅僅是當(dāng)時的HotSpot虛擬機設(shè)計團(tuán)隊選擇把收集器的分代設(shè)計擴展至方法區(qū),或者說使用永久代來實現(xiàn)方法區(qū)而已,這樣使得 HotSpot的垃圾收集器能夠像管理Java堆一樣管理這部分內(nèi)存,省去專門為方法區(qū)編寫內(nèi)存管理代碼的工作。

相對Java堆而言,垃圾收集行為在這個區(qū)域的確是比較少出現(xiàn)的,但并非數(shù)據(jù)進(jìn)入了方法區(qū)就永久”存在了。 這區(qū)域的內(nèi)存回收目標(biāo)主要是針對常量池的回收和對類型的卸載,一般來說這個區(qū)域的回收效果比較難令人滿意,尤其是類型的卸載,條件相當(dāng)苛刻,但是這部分區(qū)域的回收有時又確實是必要的。

運行時常量池

運行時常量池是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池表,用于存放編譯期生成的各種字面量與符號引用,在類加載后存放到方法區(qū)的運行時常量池中。

運行時常量池相對于Class文件常量池的另外一個重要特征是具備動態(tài)性,Java語言并不要求常量一定只有編譯期才能產(chǎn)生,也就是說,并非預(yù)置入Class文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運行時常量池,運行期間也可以將新的常量放入池中,這種特性被開發(fā)人員利用得比較多的便是String類的 intern()方法。

深入解析String#intern

Java中,直接使用雙引號聲明出來的String對象會直接存儲在常量池中。不是用雙引號聲明的String對象,可以使用String提供的intern方法。

intern 方法:如果字符串常量池中已經(jīng)包含一個等于此String對象的字符串,則返回代表池中這個字符串的String對象的引用;否則,會將此String對象包含的字符串添加 到常量池中,并且返回此String對象的引用。

小結(jié)

整理下上面介紹的JVM運行時數(shù)據(jù)區(qū)域:

JVM翻越內(nèi)存管理的墻是什么

JVM垃圾回收機制

上面介紹了程序計數(shù)器、虛擬機棧、本地方法棧都是線程私有區(qū)域,這三個區(qū)域隨線程而生,隨線程而滅。 在這幾個區(qū)域內(nèi)就不需要過多考慮如何回收的問題,當(dāng)方法結(jié)束或者線程結(jié)束時,內(nèi)存自然就跟隨著回收了。

比如棧中的棧幀隨著方法的進(jìn)入和退出而有條不紊地執(zhí)行著出棧和入棧操作。每一個棧幀中分配多少內(nèi)存基本上是在類結(jié)構(gòu)確定下來時就已知的(盡管在運行期會由即時編譯器進(jìn)行一些優(yōu)化,但在基于概念模型的討論里,大體上可以認(rèn)為是編譯期可知的),因此這幾個區(qū)域的內(nèi)存分配和回收都具備確定性。

但是Java堆和方法區(qū)這兩個區(qū)域則有著很顯著的不確定性:

1.一個接口的多個實現(xiàn)類需要的內(nèi)存可能會不一樣,一個方法所執(zhí)行的不同條件分支所需要的內(nèi)存也可能不一樣,只有處于運行期間,我們才能知道程序究竟會創(chuàng)建哪些對象,創(chuàng)建多少個對象,這部分內(nèi)存的分配和回收是動態(tài)的。垃圾收集器所關(guān)注的正是這部分內(nèi)存該如何管理。

2.方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:廢棄的常量和不再使用的類型?;厥諒U棄常量與回收 Java堆中的對象非常類似。

比如已經(jīng)沒有任何字符串對象引用常量池中的某常量,且虛擬機中也沒有其他地方引用這個字面量。如果在這時發(fā)生內(nèi)存回收,而且垃圾收集器判斷確有必要的話,該常量就將會被系統(tǒng)清理出常量池。常量池中其他類(接口)、方法、字段的符號引用也與此類似。

方法區(qū)垃圾收集的“性價比”通常也是比較低的:在Java堆中,尤其是在新生代中,對常規(guī)應(yīng)用進(jìn)行一次垃圾收集通常可以回收70%至99%的內(nèi)存空間,相比之下,方法區(qū)回收囿于苛刻的判定條件,其區(qū)域垃圾收集的回收成果往往遠(yuǎn)低于此。

判斷對象存活

垃圾回收的是死亡的對象,所以在回收前要做的事確定這個對象是否還存活。判斷對象存活的方式主流的有兩種算法:引用計數(shù)算法和可達(dá)性分析算法。

引用計數(shù)算法

在對象中添加一個引用計數(shù)器,每當(dāng)有一個地方引用它時,計數(shù)器值就加一;當(dāng)引用失效時,計數(shù)器值就減一。任何時刻計數(shù)器為零的對象就是不可能再被使用的。

該算法的缺點是:當(dāng)兩個對象互相引用,會導(dǎo)致無法回收;因為互相引用著對方,導(dǎo)致它們的引用計數(shù)都不為零,引用計數(shù)算法也就無法回收它們。

引用計數(shù)算法(Reference Counting)雖然占用了一些額外的內(nèi)存空間來進(jìn)行計數(shù),但 它的原理簡單,判定效率也很高,在大多數(shù)情況下它都是一個不錯的算法。也有一些比較著名的應(yīng)用 案例,例如微軟COM(Component Object Model)技術(shù)、使用ActionScript 3的FlashPlayer、Python語言以及在游戲腳本領(lǐng)域得到許多應(yīng)用的Squirrel中都使用了引用計數(shù)算法進(jìn)行內(nèi)存管理。但是,在Java 領(lǐng)域,至少主流的Java虛擬機里面都沒有選用引用計數(shù)算法來管理內(nèi)存。

可達(dá)性分析算法

當(dāng)前主流的商用程序語言(Java、C#,Lisp)的內(nèi)存管理子系統(tǒng),都是通過可達(dá)性分析(Reachability Analysis)算法來判定對象是否存活的。

該算法的基本思路就是通過一系列稱為“GC Roots”的根對象作為起始節(jié)點集,從這些節(jié)點開始,根據(jù)引用關(guān)系向下搜索,搜索過程所走過的路徑稱為“引用鏈”(Reference Chain),如果某個對象到GC Roots間沒有任何引用鏈相連, 或者用圖論的話來說就是從GC Roots到這個對象不可達(dá)時,則證明此對象是不可能再被使用的。

JVM翻越內(nèi)存管理的墻是什么

其中GC Root的對象有很多種,常見的有:

  • 在虛擬機棧(棧幀中的本地變量表)中引用的對象,譬如各個線程被調(diào)用的方法堆棧中使用到的參數(shù)、局部變量、臨時變量等。

  • 在方法區(qū)中類靜態(tài)屬性引用的對象,譬如Java類的引用類型靜態(tài)變量。

  • 在方法區(qū)中常量引用的對象,譬如字符串常量池(String Table)里的引用。

  • 在本地方法棧中JNI(即通常所說的Native方法)引用的對象。

  • 所有被同步鎖(synchronized)持有的對象

幾種引用方式

無論是通過引用計數(shù)算法判斷對象的引用數(shù)量,還是通過可達(dá)性分析算法判斷對象是否引用鏈可達(dá),判定對象是否存活都和“引用”離不開關(guān)系。

根據(jù)引起的強度從強到弱排序:

  • 強引用:強引用是我們最常用的,在程序代碼之中普遍存在的引用賦值,即類似“Object obj=new Object()”這種引用關(guān)系。無論任何情況下,只要強引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會回收掉被引用的對象。

  • 軟引用:描述一些還有用,但非必須的對象。只被軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常前,會把這些對象列進(jìn)回收范圍之中進(jìn)行第二次回收,如果這次回收還沒有足夠的內(nèi)存, 才會拋出內(nèi)存溢出異常。

  • 弱引用:用來描述那些非必須對象,但是它的強度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生為止。當(dāng)垃圾收集器開始工作,無論當(dāng)前內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象。

  • 虛引用:也稱為“幽靈引用”或者“幻影引用”,它是最弱的一種引用關(guān)系。一個對象是否有虛引用的存在,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用來取得一個對象實例。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的只是為了能在這個對象被收集器回收時收到一個系統(tǒng)通知。

垃圾回收算法

標(biāo)記清除算法

算法分為“標(biāo)記”和“清除”兩個階段:首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后,統(tǒng)一回收掉所有被標(biāo)記的對象,也可以反過來,標(biāo)記存活的對象,統(tǒng)一回收所有未被標(biāo)記的對象。

缺點:

  • 執(zhí)行效率不穩(wěn)定,如果Java堆中包含大量對象,而且其中大部分是需要被回收的,這時必須進(jìn)行大量標(biāo)記和清除的動作,導(dǎo)致標(biāo)記和清除兩個過程的執(zhí)行效率都隨對象數(shù)量增長而降低

  • 內(nèi)存空間的碎片化問題,標(biāo)記、清除之后會產(chǎn)生大 量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會導(dǎo)致當(dāng)以后在程序運行過程中需要分配較大對象時無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作

標(biāo)記復(fù)制算法

它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。

優(yōu)點:

解決標(biāo)記清除法的缺點。每次都是對整個半?yún)^(qū)進(jìn)行回收,不用考慮內(nèi)存碎片浪費。

缺點:

  • 缺陷在于將可用內(nèi)存縮小為了原來的一半,空間浪費未免太多了一點。

  • 如果內(nèi)存中多數(shù)對象都是存活的,這種算法將會產(chǎn)生大量的內(nèi)存間復(fù)制的開銷。

現(xiàn)在的商用Java虛擬機大多都優(yōu)先采用了這種收集算法去回收新生代。

新生代中的對象有98%熬不過第一輪收集。因此并不需要按照1∶1的比例來劃分新生代的內(nèi)存空間。HotSpot虛擬機的Serial、ParNew等新生代收集器均采用了這種策略來設(shè) 計新生代的內(nèi)存布局。Appel式回收的具體做法是把新生代分為一塊較大的Eden空間和兩塊較小的 Survivor空間,每次分配內(nèi)存只使用Eden和其中一塊Survivor。發(fā)生垃圾搜集時,將Eden和Survivor中仍 然存活的對象一次性復(fù)制到另外一塊Survivor空間上,然后直接清理掉Eden和已用過的那塊Survivor空 間。HotSpot虛擬機默認(rèn)Eden和Survivor的大小比例是8∶1,也即每次新生代中可用內(nèi)存空間為整個新 生代容量的90%(Eden的80%加上一個Survivor的10%),只有一個Survivor空間,即10%的新生代是會 被“浪費”的。

標(biāo)記整理法

該算法讓所有存活的對象都向內(nèi)存空間一端移動,然后直接清理掉邊界以外的內(nèi)存。

優(yōu)點:不會存在標(biāo)記整理內(nèi)存浪費的問題。

缺點:復(fù)制收集算法在對象存活率高的情況下就會出現(xiàn)復(fù)制操作,移動操作多,效率會變低。

標(biāo)記清除法和標(biāo)記整理法的選擇是一種權(quán)衡:

標(biāo)記整理法,通過移動存活對象,尤其是在老年代這種每次回收都有大量對象存活區(qū)域,移動存活對象并更新所有引用這些對象的地方將會是一種極為負(fù)重的操作,而且這種對象移動操作必須**全程暫停用戶應(yīng)用程序(Stop The World)**才能進(jìn)行。

如果跟標(biāo)記-清除算法那樣完全不考慮移動和整理存活對象的話,彌散于堆中的存活對象導(dǎo)致的空間碎片化問題就只能依賴更為復(fù)雜的內(nèi)存分配器和內(nèi)存訪問器來解決。譬如通過“分區(qū)空閑分配鏈表”來解決內(nèi)存分配問題(計算機硬盤存儲大文件就不要求物理連續(xù)的磁盤空間,能夠在碎片化的硬盤上存儲和訪問就是通過硬盤分區(qū)表實現(xiàn)的)。內(nèi)存的訪問是用戶程序最頻繁的操作,假如在這個環(huán)節(jié)上增加了額外的負(fù)擔(dān),勢必會直接影響應(yīng)用程序的吞吐量。

基于以上兩點,是否移動對象都存在弊端,移動則內(nèi)存回收時會更復(fù)雜,不移動則內(nèi)存分配時會更復(fù)雜。從垃圾收集的停頓時間來看,不移動對象停頓時間會更短,甚至可以不需要停頓,但是從整個程序的吞吐量來看,移動對象會更劃算。

即使不移動對象會使得收集器的效率提升一些,但因內(nèi)存分配和訪問相比垃圾收集頻率要高得多,這部分的耗時增加,總吞吐量仍然是下降的。

HotSpot虛擬機里面關(guān)注吞吐量的Parallel Scavenge收集器是基于標(biāo)記-整理算法的,而關(guān)注延遲的CMS收集器則是基于標(biāo)記-清除算法的,

為了平衡二者的弊端,就有一種中和的方式。讓虛擬機平時多數(shù)時間都采用標(biāo)記-清除算法,暫時容忍內(nèi)存碎片的存在,直到內(nèi)存空間的碎片化程度已經(jīng)大到影響對象分配時,再采用標(biāo)記-整理算法收集一次,以獲得規(guī)整的內(nèi)存空間。比如基于標(biāo)記-清除算法的CMS收集器面臨空間碎片過多時采用的就是這種處理辦法。

分代收集算法

當(dāng)前商業(yè)虛擬機的垃圾收集器,大多數(shù)都遵循了“分代收集”的理論進(jìn)行設(shè)計。

多款常用的垃圾收集器的一致的設(shè)計原則:收集器應(yīng)該將Java堆劃分出不同的區(qū)域,然后將回收對象依據(jù)其年齡(年齡即對象熬過垃圾收集過程的次數(shù))分配到不同的區(qū)域之中存儲。

這樣做的優(yōu)點是:

  • 如果一個區(qū)域中大多數(shù)對象都難以熬過垃圾收集過程的話,那么把它們集中放在一起,每次回收時只關(guān)注如何保留少量存活而不是去標(biāo)記那些大量將要被回收的對象,就能以較低代價回收到大量的空間。

  • 如果剩下的都是難以消亡的對象,那把它們集中放在一塊, 虛擬機便可以使用較低的頻率來回收這個區(qū)域,這就同時兼顧了垃圾收集的時間開銷和內(nèi)存的空間有效利用。

Java堆劃分出不同的區(qū)域之后,垃圾收集器才可以每次只回收其中某一個或者某些部分的區(qū)域 。因而才有了Minor GC,Major GC,Full GC這樣的回收類型的劃分。也才能夠針對不同的區(qū)域安排與里面存儲對象存亡特征相匹配的垃圾收集算法。

收集概念的區(qū)分:

新生代收集(Minor GC/Young GC):指目標(biāo)只是新生代的垃圾收集

老年代收集(Major GC/Old GC):指目標(biāo)只是老年代的垃圾收集。請注意“Major GC”這個說法現(xiàn)在有點混淆,在不同資料上常有不同所指, 讀者需按上下文區(qū)分到底是指老年代的收集還是整堆收集。

整堆收集(Full GC):收集整個Java堆和方法區(qū)的垃圾收集。

Java堆·劃分為新生代和老年代。在新生代中,每次垃圾收集時都發(fā)現(xiàn)有大批對象死去,而每次回收后存活的少量對象,將會逐步晉升到老年代中存放。

ps: 這些區(qū)域劃分僅僅是一部分垃圾收集器的共同特性或者說設(shè)計風(fēng)格而已,而非某個JVM具體實現(xiàn)的固有內(nèi)存布局,更不是《Java虛擬機規(guī)范》里對Java堆的進(jìn)一步細(xì)致劃分。作為業(yè)界絕對主流的HotSpot虛擬機,它內(nèi)部的垃圾收集器全部都基于“經(jīng)典分代” 來設(shè)計,需要新生代、老年代收集器搭配才能工作。但到了今天,HotSpot里面也出現(xiàn)了不采用分代設(shè)計的新垃圾收集器。

內(nèi)存回收策略

下面介紹的回收策略是基于“經(jīng)典分代” 設(shè)計的回收過程:

1.新生代的分配和回收

1.大多數(shù)情況下,對象在新生代Eden區(qū)中分配。當(dāng)Eden區(qū)沒有足夠空間進(jìn)行分配時,虛擬機將發(fā)起一次Minor GC。把新生代分為一塊較大的Eden空間和兩塊較小的 Survivor空間,每次分配內(nèi)存只使用Eden和其中一塊Survivor。發(fā)生垃圾搜集時,將Eden和Survivor中仍 然存活的對象一次性復(fù)制到另外一塊Survivor空間上,然后直接清理掉Eden和已用過的那塊Survivor空 間。HotSpot虛擬機默認(rèn)Eden和Survivor的大小比例是8∶1

2.大對象直接進(jìn)入老年代

2.大對象直接進(jìn)入老年代。大對象就是指需要大量連續(xù)內(nèi)存空間的Java對象,最典型的大對象便是那種很長的字符串,或者元素數(shù)量很龐大的數(shù)組。

為什么要這么做呢?這樣做的目的就是避免在Eden區(qū)及兩個Survivor區(qū)之間來回復(fù)制,產(chǎn)生大量的內(nèi)存復(fù)制操作。

大對象對虛擬機的內(nèi)存分配來說是一個壞消息,比遇到一個大對象更壞的消息就是遇到一群“朝生夕滅”的短命大對象。我們寫程序的時候應(yīng)注意避免大對象。在Java虛擬機中要避免大對象的原因是,在分配空間時,它容易導(dǎo)致內(nèi)存明明還有不少空間時就提前觸發(fā)垃圾收集,以獲取足夠的連續(xù)空間才能安置好它們,而當(dāng)復(fù) 制對象時,大對象就意味著高額的內(nèi)存復(fù)制開銷。

3.長期存活的對象將進(jìn)入老年代

如果經(jīng)過第一次Minor GC后仍然存活,并且能被Survivor容納的話,該對象會被移動到Survivor空間中,并且將其對象年齡設(shè)為1歲。對象在Survivor區(qū)中每熬過一次Minor GC,年齡就增加1歲,當(dāng)它的年齡增加到一定的年齡閾值(默認(rèn)為15),就會被晉升到老年代中。對象晉升老年代的年齡閾值,可以通過參數(shù)-XX: MaxTenuringThreshold設(shè)置。

關(guān)于“JVM翻越內(nèi)存管理的墻是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

jvm
AI