溫馨提示×

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

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

分析Java內(nèi)存管理與垃圾回收

發(fā)布時(shí)間:2021-11-05 14:26:24 來源:億速云 閱讀:122 作者:iii 欄目:web開發(fā)

這篇文章主要介紹“分析Java內(nèi)存管理與垃圾回收”,在日常操作中,相信很多人在分析Java內(nèi)存管理與垃圾回收問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”分析Java內(nèi)存管理與垃圾回收”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

Java是在JVM所虛擬出的內(nèi)存環(huán)境中運(yùn)行的。內(nèi)存分為棧(stack)和堆(heap)兩部分。

棧的基本概念參考 紙上談兵: 棧 (stack)。許多語(yǔ)言利用棧數(shù)據(jù)結(jié)構(gòu)來記錄函數(shù)調(diào)用的次序和相關(guān)變量(參考 Linux從程序到進(jìn)程)。

在Java中,JVM中的棧記錄了線程的方法調(diào)用。每個(gè)線程擁有一個(gè)棧。在某個(gè)線程的運(yùn)行過程中,如果有新的方法調(diào)用,那么該線程對(duì)應(yīng)的棧就會(huì)增加一個(gè)存儲(chǔ)單元,即幀(frame)。在frame中,保存有該方法調(diào)用的參數(shù)、局部變量和返回地址。

分析Java內(nèi)存管理與垃圾回收

調(diào)用棧

Java的參數(shù)和局部變量只能是基本類型的變量(比如int),或者對(duì)象的引用(reference)。因此,在棧中,只保存有基本類型的變量和對(duì)象引用。

引用所指向的對(duì)象保存在堆中。(引用可能為Null值,即不指向任何對(duì)象)

分析Java內(nèi)存管理與垃圾回收

引用與對(duì)象

當(dāng)被調(diào)用方法運(yùn)行結(jié)束時(shí),該方法對(duì)應(yīng)的幀將被刪除,參數(shù)和局部變量所占據(jù)的空間也隨之釋放。線程回到原方法,繼續(xù)執(zhí)行。當(dāng)所有的棧都清空時(shí),程序也隨之運(yùn)行結(jié)束。

如上所述,棧(stack)可以自己照顧自己。但堆必須要小心對(duì)待。堆是JVM中一塊可自由分配給對(duì)象的區(qū)域。當(dāng)我們談?wù)摾厥?garbage collection)時(shí),我們主要回收堆(heap)的空間。

Java的普通對(duì)象存活在堆中。與棧不同,堆的空間不會(huì)隨著方法調(diào)用結(jié)束而清空。因此,在某個(gè)方法中創(chuàng)建的對(duì)象,可以在方法調(diào)用結(jié)束之后,繼續(xù)存在于堆中。這帶來的一個(gè)問題是,如果我們不斷的創(chuàng)建新的對(duì)象,內(nèi)存空間將最終消耗殆盡。

垃圾回收

垃圾回收(garbage collection,簡(jiǎn)稱GC)可以自動(dòng)清空堆中不再使用的對(duì)象。垃圾回收機(jī)制最早出現(xiàn)于1959年,被用于解決Lisp語(yǔ)言中的問題。垃圾回收是Java的一大特征。并不是所有的語(yǔ)言都有垃圾回收功能。比如在C/C++中,并沒有垃圾回收的機(jī)制。程序員需要手動(dòng)釋放堆中的內(nèi)存。

由于不需要手動(dòng)釋放內(nèi)存,程序員在編程中也可以減少犯錯(cuò)的機(jī)會(huì)。利用垃圾回收,程序員可以避免一些指針和內(nèi)存泄露相關(guān)的bug(這一類bug通常很隱蔽)。但另一方面,垃圾回收需要耗費(fèi)更多的計(jì)算時(shí)間。垃圾回收實(shí)際上是將原本屬于程序員的責(zé)任轉(zhuǎn)移給計(jì)算機(jī)。使用垃圾回收的程序需要更長(zhǎng)的運(yùn)行時(shí)間。

在Java中,對(duì)象的是通過引用使用的(把對(duì)象相像成致命的毒物,引用就像是用于提取毒物的鑷子)。如果不再有引用指向?qū)ο?,那么我們就再也無從調(diào)用或者處理該對(duì)象。這樣的對(duì)象將不可到達(dá)(unreachable)。垃圾回收用于釋放不可到達(dá)對(duì)象所占據(jù)的內(nèi)存。這是垃圾回收的基本原則。

(不可到達(dá)對(duì)象是死對(duì)象,是垃圾回收所要回收的垃圾)

早期的垃圾回收采用引用計(jì)數(shù)(reference counting)的機(jī)制。每個(gè)對(duì)象包含一個(gè)計(jì)數(shù)器。當(dāng)有新的指向該對(duì)象的引用時(shí),計(jì)數(shù)器加1。當(dāng)引用移除時(shí),計(jì)數(shù)器減1。當(dāng)計(jì)數(shù)器為0時(shí),認(rèn)為該對(duì)象可以進(jìn)行垃圾回收。

然而,一個(gè)可能的問題是,如果有兩個(gè)對(duì)象循環(huán)引用(cyclic reference),比如兩個(gè)對(duì)象互相引用,而且此時(shí)沒有其它(指向A或者指向B)的引用,我們實(shí)際上根本無法通過引用到達(dá)這兩個(gè)對(duì)象。

因此,我們以棧和static數(shù)據(jù)為根(root),從根出發(fā),跟隨所有的引用,就可以找到所有的可到達(dá)對(duì)象。也就是說,一個(gè)可到達(dá)對(duì)象,一定被根引用,或者被其他可到達(dá)對(duì)象引用。

分析Java內(nèi)存管理與垃圾回收

橙色,可到達(dá);綠色,不可到達(dá)

JVM實(shí)施

JVM的垃圾回收是多種機(jī)制的混合。JVM會(huì)根據(jù)程序運(yùn)行狀況,自行決定采用哪種垃圾回收。

我們先來了解"mark and sweep"。這種機(jī)制下,每個(gè)對(duì)象將有標(biāo)記信息,用于表示該對(duì)象是否可到達(dá)。當(dāng)垃圾回收啟動(dòng)時(shí),Java程序暫停運(yùn)行。JVM從根出發(fā),找到所有的可到達(dá)對(duì)象,并標(biāo)記(mark)。隨后,JVM需要掃描整個(gè)堆,找到剩余的對(duì)象,并清空這些對(duì)象所占據(jù)的內(nèi)存。

另一種是"copy and sweep"。這種機(jī)制下,堆被分為兩個(gè)區(qū)域。對(duì)象總存活于兩個(gè)區(qū)域中的一個(gè)。當(dāng)垃圾回收啟動(dòng)時(shí),Java程序暫停運(yùn)行。JVM從根出發(fā),找到可到達(dá)對(duì)象,將可到達(dá)對(duì)象復(fù)制到空白區(qū)域中并緊密排列,修改由于對(duì)象移動(dòng)所造成的引用地址的變化。最后,直接清空對(duì)象原先存活的整個(gè)區(qū)域,使其成為新的空白區(qū)域。

可以看到,"copy and sweep"需要更加復(fù)雜的操作,但也讓對(duì)象可以緊密排列,避免"mark and sweep"中可能出現(xiàn)的空隙。在新建對(duì)象時(shí),"copy and sweep"可以提供大塊的連續(xù)空間。因此,如果對(duì)象都比較"長(zhǎng)壽",那么適用于"mark and sweep"。如果對(duì)象的"新陳代謝"比較活躍,那么適用于"copy and sweep"。

上面兩種機(jī)制是通過分代回收(generational collection)混合在一起的。每個(gè)對(duì)象記錄有它的世代(generation)信息。所謂的世代,是指該對(duì)象所經(jīng)歷的垃圾回收的次數(shù)。世代越久遠(yuǎn)的對(duì)象,在內(nèi)存中存活的時(shí)間越久。

根據(jù)對(duì)Java程序的統(tǒng)計(jì)觀察,世代越久的對(duì)象,越不可能被垃圾回收(富人越富,窮人越窮)。因此,當(dāng)我們?cè)诶厥諘r(shí),要更多關(guān)注那些年輕的對(duì)象。

現(xiàn)在,具體看一下JVM中的堆:

分析Java內(nèi)存管理與垃圾回收

我們看到,堆分為三代。其中的永久世代(permanent generation)中存活的是Class對(duì)象。這些對(duì)象不會(huì)被垃圾回收。我們?cè)? RTTI中已經(jīng)了解到,每個(gè)Class對(duì)象代表一個(gè)類,包含有類相關(guān)的數(shù)據(jù)與方法,并提供類定義的代碼。每個(gè)對(duì)象在創(chuàng)建時(shí),都要參照相應(yīng)的Class對(duì)象。每個(gè)對(duì)象都包含有指向其對(duì)應(yīng)Class對(duì)象的引用。

年輕世代(young generation)和成熟世代(tenured generation)需要進(jìn)行垃圾回收。年輕世代中的對(duì)象世代較近,而成熟世代中的對(duì)象世代較久。

分析Java內(nèi)存管理與垃圾回收

世代

年輕世代進(jìn)一步分為三個(gè)區(qū)域

eden(伊甸): 新生對(duì)象存活于該區(qū)域。新生對(duì)象指從上次GC后新建的對(duì)象。

分析Java內(nèi)存管理與垃圾回收

新生對(duì)象生活于伊甸園

from, to: 這兩個(gè)區(qū)域大小相等,相當(dāng)于copy and sweep中的兩個(gè)區(qū)域。

當(dāng)新建對(duì)象無法放入eden區(qū)時(shí),將出發(fā)minor collection。JVM采用copy and sweep的策略,將eden區(qū)與from區(qū)的可到達(dá)對(duì)象復(fù)制到to區(qū)。經(jīng)過一次垃圾回收,eden區(qū)和from區(qū)清空,to區(qū)中則緊密的存放著存活對(duì)象。隨后,from區(qū)成為新的to區(qū), to區(qū)成為新的from區(qū)。

如果進(jìn)行minor collection的時(shí)候,發(fā)現(xiàn)to區(qū)放不下,則將部分對(duì)象放入成熟世代。另一方面,即使to區(qū)沒有滿,JVM依然會(huì)移動(dòng)世代足夠久遠(yuǎn)的對(duì)象到成熟世代。

如果成熟世代放滿對(duì)象,無法移入新的對(duì)象,那么將觸發(fā)major collection。JVM采用mark and sweep的策略,對(duì)成熟世代進(jìn)行垃圾回收。

到此,關(guān)于“分析Java內(nèi)存管理與垃圾回收”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

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

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

AI