目錄
一、棧針
二、java 對象內(nèi)存分布
1、那何為java內(nèi)存對象布局?
2、什么是jvm的內(nèi)存模型
1、如果我們新生代,一直創(chuàng)建新對象,此時我們新生代不夠用了怎么辦?
2、那么為什么大部分對象的生命周期比較短呢?這個結(jié)論哪來的呢?
3、那么為什么是這個8:1:1呢?
4、就是萬一我們s0或s1不夠了怎么辦?
5、細聊為什么young分為eden,s0,s1區(qū)?
三、總結(jié)
1、垃圾回收
上一節(jié)課,我們探討了jvm每塊內(nèi)存區(qū)域,但是我們最后對于棧針探究還沒有深入,對于有開發(fā)經(jīng)驗的人來說,棧針也是值得研究掌握的,那么棧針到底是什么?到底包含了什么?也就是說我們一個方法被調(diào)用執(zhí)行壓入這樣一個棧當中,這樣一個棧針的細節(jié)到底有什么?這就是我們應該去思考的。
一、棧針
首先官網(wǎng)他也有一個說明:第 2 章。Java 虛擬機的結(jié)構(gòu) (oracle.com)
首先官網(wǎng)說棧針包含以下幾個內(nèi)容;
- 動態(tài)鏈接
- 方法返回值地址
- 局部變量表
- 操作數(shù)棧
這就是我們方法對應棧針它所包含的幾個點。
他用一張圖的表示其實就是下面這張圖:
局部變量表:就是保存我們局部變量的
操作數(shù)棧:就是對我們操作數(shù)進行出棧和入棧的一個操作的。就是我們各個方法的操作數(shù)。
動態(tài)鏈接:可以簡單理解為:我們程序在運行的時候,你的某一些類型才會進行一個確定(就是跟我們類記載中,有些類型是在程序運行的時候才會知道,或者多態(tài)中,只有被加載是你才只要要使用哪一個類,的意思是一樣的);可以理解為,jvm支持在你運行的時候再去確定你的類型等。
方法返回地址:就是說如果我在函數(shù)當中,當一個方法調(diào)用另一個方法調(diào)用完了,這個方法應該繼續(xù)從哪邊往下繼續(xù)去執(zhí)行。這是要去記錄方法的返回地址,
如果還不理解,有如下圖:
這個就詳細的描述了一個棧針到底應該包含哪些部分。
解讀源代碼是:
通過上面一個圖解,我們能夠很好地理解棧針里面的東西。
那么此時有一個疑惑:
運行時數(shù)據(jù)區(qū)跟我們虛擬機棧之間有什么關系呢?
例如此時,我們在方法只能有一個Object 的對象變量,那么此時他怎么存儲的呢?
這個Object obj的這個變量,依舊在我們局部變量表中,但是他的真正的實例是在我們的堆中。所以這個時候,就會有一個經(jīng)典的點,就是我們當前局部變量表中的這樣一個變量:叫Obj這樣一個變量,他會去進行把局部變量表中的這個obj變量,指向我們的一個堆。如果說我們把java虛擬機棧和我們的一個運行時數(shù)據(jù)區(qū)去聯(lián)系起來的話,我們會去得到下面這樣一張圖:
我們會看到:如果我們有一個obj這樣一個變量,他還會在局部變量表中,只不過他真正指向的使我們的一個堆內(nèi)存中的new Object().
此時我們再往下面走:看這張圖你會感覺比較有意思:
左邊是你一個進程當中線程的一個執(zhí)行。線程這一塊執(zhí)行的話,你會發(fā)現(xiàn)局部變量表中會有一個典型的就是,棧里面這樣的一些元素指向了堆內(nèi)存。我們對照這個圖去進行延伸的話,
我可以怎么去做一個延伸呢?既然棧能進行去指向堆。那么我還有一個疑問?
假如說:我們在類中創(chuàng)建一個全局的靜態(tài)Object 的變量。我們知道靜態(tài)變量是存在我們方法區(qū)的。也就是說:方法區(qū)里面的元素指向的堆。
那么我們就發(fā)現(xiàn)是這樣一種形式了,你方法區(qū)有一個靜態(tài)變量,這個靜態(tài)變量真正指向的內(nèi)存地址使我們的一個堆內(nèi)存中地址,這就是經(jīng)典的方法區(qū)里面的元素指向的堆內(nèi)存。
我們可以根據(jù)這張圖去感受一下:
既然我們虛擬機棧能指向堆,方法區(qū)也能指向堆,那么我們可能會有一個問題:
堆能指向方法區(qū)嗎?
就是說有沒有這樣一種可能,這樣一個現(xiàn)象,堆能指向方法區(qū)呢?
我們慢慢去思考,我們普通的成員變量,他會隨著這樣一個object對象他會存儲在堆內(nèi)存中嗎?
有一種可能性,或者我們能夠想出來的可能性就是:
我 new Preson()這樣一個Java對象,而且我可以確定的是Preson類型的java對象,你說如果我在new Preson()對象,他也是一個Proson類型的java對象,這時候我就會不經(jīng)意的思考一個問題:
我怎么知道我們創(chuàng)建的Preson()就屬于Prosen類型呢?
哎,這個類型信息的數(shù)據(jù)是存在那里?
回想一下,我們方法區(qū)會去存儲類型的信息。
如果此時我在堆中不斷去創(chuàng)建Preson()對象的話,我怎么知道我屬于Preson類型。也就是說我當前這個Preson()是指向方法區(qū)中Preson的信息的。我認為一定是這樣。
那么我們怎么去證明?
我們證明的點就是每一個Java對象里面應該需要去維護一個東西。這個東西一定知道你是從那邊出生的。就好比是在你這堆對象實例中好比有一個信息能夠維護住你是屬于哪一個Preson這樣一個類型的。
那么有嘛?如果我是一個java設計者,我就在類對象中去創(chuàng)建這樣一個屬性去標識。但是我們沒必要這樣去做,因為在對象或者jvm這樣的一個設計者,他們也想好了,你的java對象不僅僅應該只有數(shù)據(jù)的這樣一部分。你除了數(shù)據(jù)的這樣一部分,你應該還包含其他信息:比如說,你當前這個對象是屬于哪一個類型的。你當前這樣一個對象,存活了多少次gc.等信息。
到這里就會涉及到面試中常會被問到的,java對象的內(nèi)存布局:我們看到類中這些變量方法等信息是我們可以直接看到。但是我們看不到的信息,就是描述了我們不可解釋的現(xiàn)象。
二、java 對象內(nèi)存分布
這樣的圖如下:
1、那何為java內(nèi)存對象布局?
就是說一個普通的java到底包含哪些部分?
他除了包含實例數(shù)據(jù)他左邊還包含一個對象頭,右邊包含對齊填充。
他對象頭會包含兩到三個這樣部分內(nèi)容。因為如果你是數(shù)組這樣一個類型的話,他會有一個length,會去記住你數(shù)組這樣的一個長度;這是數(shù)組所特有的。然后正常的對象,他在對象頭里面會包含兩個部分:
一個叫:Markwork,如圖
至此,我們回頭想一想,我們探討了虛擬機棧,但在會看看左邊這個部分,java虛擬機棧是跟java線程有關系的,雖然也存儲數(shù)據(jù),但生命周期比較短,。
所以我們要去看看我們存儲數(shù)據(jù)的,兩個區(qū)域:方法區(qū)和堆,他們是和進程有關系的。而這兩個區(qū)域我們很能夠去聯(lián)想到內(nèi)存模型,這個內(nèi)存模型是jvm的內(nèi)存模型,而不是所謂的java的內(nèi)存模型,就是我們看到的就是運行時,那么這兩塊數(shù)據(jù)他們落地到底是什么樣的呢,如何體現(xiàn)的呢?所以此時我么就需要去關注jvm的內(nèi)存模型了。此時我們就會想到這張圖:
首先,我們先看這個,以前很老的資料說這個就是java的內(nèi)存模型,這個是不對的。我們從官網(wǎng)也得到了證實,他更確切的是jvm運行時數(shù)據(jù)區(qū)
資料流傳的太多了,他其實叫java運行時數(shù)據(jù)區(qū)。
然后我們現(xiàn)在要去討論的維度,雖然官方?jīng)]有得到證實,但是我跟愿意稱之為JVM的內(nèi)存模型,
什么是jvm的內(nèi)存模型?什么意思?
2、什么是jvm的內(nèi)存模型
官網(wǎng)沒事有找到這個說明,但是有一個點可以確定,上面這個叫做運行時狀態(tài),如果他不是運行時狀態(tài),那么他應該有真實物理內(nèi)存分布。所以這個真正的內(nèi)存模型的落地更愿意叫他jvm的內(nèi)存模型。而這個內(nèi)存模型的話按道理說,應該把上面五個部分統(tǒng)統(tǒng)給他落地。但是這個部分:
jvm內(nèi)存模型為什么沒有,為什么大家沒有去做討論?我認為他是根據(jù)線程的生命周期是相關的,線程一定是在創(chuàng)建運行時才會去更多的去討論他的。但是你的程序即使不運行起來,這兩塊區(qū)域他也是存在的。因為我們現(xiàn)在是討輪真正全部物理的落地狀態(tài),所以我們這個可以不去討論,因為即使你這兩個不是運行時狀態(tài),這兩個隨著虛擬機的創(chuàng)建就已經(jīng)存在了,這時候就會去形成一個jvm待測內(nèi)存模型。這個jvm的內(nèi)存模型就是我們常說的:
左邊是一個mateSpace, 右邊是一個堆。這個只是個人理解,可以去參考下。然后還有一個java內(nèi)存模型:叫做JMM,我認為這個東西更準確的應該是形容我們一個工作總內(nèi)存,下面會有一個個工作內(nèi)存這樣的一個情況。這個是個人一個理解,但是你知道是一個怎么樣的情況就可以了。
此時(java 對象內(nèi)存分布)我們真正的而要把這兩塊落地的是:Mate space和heap
那么我們要把它落地?但是此時怎么樣去把他落地呢?
此時我們就需要去設計一個區(qū)域,把堆內(nèi)存和方法區(qū)去進行一個落地。那么怎么樣來落地?我們來畫圖:
那么假如我們式設計者,我們應該怎么去設計呢?
其實我這兩個部分,不就是去存儲數(shù)據(jù)的嗎?要內(nèi)去存儲我們這類的數(shù)據(jù),比如常量,變量啊等等。要么存儲對象的數(shù)據(jù)。
假如我們有一個對象,那么我們肯定存在堆里面,那么首先我們肯定需要一個堆的區(qū)域。還有一個方法區(qū):
但是當我們對象來的時候嗎,他會存儲在堆。但是他不能直接去存,因為如果都鋪滿我們的堆,當有些對象老了,該回收了,垃圾回收就要便利整個堆所有對象。所以我不能這么去存。所以我根據(jù)年齡去分別存儲對象,所以就有了劃分老年代和新生代。你的年紀大就去老年代,不大就是新生代。這樣的一個而好處就是:比如你回收15次還回收不到。你就會去old區(qū)。當我們新創(chuàng)建一個就拿到y(tǒng)oung區(qū),我們新創(chuàng)建就會拿到y(tǒng)oung區(qū),那么我們老年代什么時候有數(shù)據(jù)呢?
老年代要有對象:
第一種情況就是:對象的大小特別大,意思就是比如我老年代有2百兆;新生代大小只有100兆空間,比如我現(xiàn)在老了一個對象是110兆,我新生代,現(xiàn)在放不下,我就直接去分配到老年代。
第二種就是:你一直在新生代去分配;分配著分配著你的對象就越來越多,你的空間不夠用了,你肯定要去進行垃圾回收(垃圾回收就是回收沒用的對象),此時我們就會把沒用的回收掉了。剩下的就是我存活了下來。那么我的年齡就會加一。直到加到15歲,所以你15次都挺過來了,此時你就不適合待在young區(qū),你就會跑到老年代中。(15歲是默認的)
那么問題來了:
1、如果我們新生代,一直創(chuàng)建新對象,此時我們新生代不夠用了怎么辦?
就要進行垃圾回收,比如回收幾個,之后如下:
此時又來一個3個單元格大的對象(就一個來了一個大的對象)
此時他進來:發(fā)現(xiàn)存儲不了了,放不下
此時就要去進行垃圾回收,但是垃圾回收又會影響我們線程,肯定會影響我們業(yè)務代碼的執(zhí)行。所以我們提倡盡量減少我們垃圾回收的評率,但是此時你看空間又不是不夠嗎。但是實際上空間是夠的,我們不是有三個空間嗎?
所以會發(fā)現(xiàn)一個問題,就是空間明明夠,但是不連續(xù),所以分配對像失敗。所以那么我們要解決這個不連續(xù),碎片問題。此時我們的新生代就要去從新去思考;
所以現(xiàn)在我們思考一下
我們怎么去進行一個思考。大家注意我們此時不僅會增加gc的評頻率,還會增加對象的年齡的增長,就加快了去老年代化。使去老年代,就更加的頻繁了,那我們怎么辦,所以我們此時就需要對新生代進行再一次劃分。分配成兩個區(qū)域:一個是eden區(qū),s(Surviror)區(qū),然后我們默認eden分配90兆大小,s區(qū)分配10兆大小,
好那么我們現(xiàn)在先去分配eden區(qū),此時我們同樣去分配,一直分配對象,此時當我們內(nèi)存不夠用了,又要去進行垃圾回收;注意:垃圾回收前提:大部分對象都是朝生夕死的。(就是大部分對象生命周期比較短)
2、那么為什么大部分對象的生命周期比較短呢?這個結(jié)論哪來的呢?
生命周期比較短就意味著,這個對象從出生到使用結(jié)束之間時間很短,很快就不用了,這種情況在我們web端很常見,比如一個訂單數(shù)據(jù),我們數(shù)據(jù)存到數(shù)據(jù)庫,就用不到了,所以對象就會被回收掉。所以幸存下來的對象一般都是很少的。此時這些對象就會存活下來,他的年齡加1.但是為了避免這些存活下來的對象不連續(xù),導致空間不連續(xù),此時要去分配內(nèi)存大小為5個格子的,但是此時又沒有這么大的,那么我們就想著把幸存下來的去進行整理,怎么辦?所以就把幸存的對象移到serviror s區(qū)中。
那么此時就會有一個優(yōu)勢:就是eden去相對連續(xù)了,不會因為少量存活的對象,而造成空間碎片。
好,那么此時又有大量數(shù)據(jù)來到eden 區(qū),當滿了時又要去進行垃圾回收,但是需要注意的是,垃圾回收是對整個young區(qū)進行回收,所以我們eden去回收后存下來一部分幾個數(shù)據(jù),同樣此時s區(qū)也被回收了幾個。但是此時eden去存活的對象需要挪到s區(qū),發(fā)現(xiàn),s區(qū)不是連續(xù)的,又存不了怎么辦?
那么我們此時怎辦呢?所以我們干脆,在進行優(yōu)化,我們的目的不就是為了我們s區(qū)能夠相對連續(xù)嗎?
所以我們此時把s區(qū)在一份為2.。兩個大小一模一樣。那么此時我們怎么分配呢?
此時我們eden就分配80兆,s去就分為兩區(qū)域一樣大的區(qū)域,s0,s1。那么此時就能解決這個問題了嗎?我們繼續(xù)分析:
假如現(xiàn)在eden區(qū)有大量的對象,滿了之后進行回收,存活的,假如我們挪到s1.當eden再一次滿了,進行垃圾回收的時候,s1區(qū)同樣也被垃圾回收了,然后和eden區(qū)都有一小部分存活下來,那么此時存活的不連續(xù)的,就往s0中挪。當再一次垃圾回收,之后,把存活的不連續(xù)的往s1中挪。哎這樣就保證了,我們有存夠連續(xù)空間去存存活的對象。
所以:此時永遠都能保證s0和s1某一個為空,這樣就能夠解決空間碎片的問題(但是這樣不會有一個弊端的嗎?就是10%的空間浪費了,浪費就浪費了,我能夠為了解決空間碎片問題,能夠最大化利用young2區(qū),我認為還是好的);所以說我們會說eden區(qū)和s0,s1他們之間的比率是8:1:1
3、那么為什么是這個8:1:1呢?
因為我們大部分的對象是在eden區(qū)存儲,所以為什么eden區(qū)是8
因為大部分的對象是朝生夕死,只有極少數(shù)的對象會存活下來,那么我把它移到存活的區(qū)域不就夠了嗎?
但是還是會存在一種情況:就是萬一我們s0或s1不夠了怎么辦?
4、就是萬一我們s0或s1不夠了怎么辦?
那么我們就要去老年代去借點內(nèi)存;這就是所謂的擔保機制。
如果說我們的s區(qū)的age大于15歲,(那么我們怎么知道s區(qū)年齡打大不大于15歲呢?因為我們對象頭中存放著對象的一個年齡)。就是說說如果我gc了15次了,我16歲了,我也移入老年代,他會這樣去做。
哎我們發(fā)現(xiàn)通過一個s區(qū)的浪費,和老年代的擔保機制,s不夠就根據(jù)年齡移入old,就可以這個問題挺好。
還有我們的old區(qū)不一定非大于young區(qū)。
5、細聊為什么young分為eden,s0,s1區(qū)?
上面這個結(jié)論是怎么得到的呢?為什么是這樣的呢?我們可以通過工具類演示,實驗一下就知道,為什么eden s0 s1是這么分配的。
工具是:jvisualvm
下載地址:VisualVM: Plugins Centers
詳細可看:
JVM-jvisualvm性能監(jiān)控可視化工具使用與eden-s0-s1分配分析_平凡之路無盡路的博客-CSDN博客
三、總結(jié)
我們從第一節(jié)課,類加載器到這里,已經(jīng)對jvm進行了一番折騰。但是jvm是不是就沒有東西可去學了?是不是不用學習了。不是,我還欠缺的一個東西是:
我們的內(nèi)存空間我們會不斷去存儲數(shù)據(jù),但是存著存著就會發(fā)現(xiàn),我們空間不夠用了,不是說你不會一直不進行回收,所以我們會感覺垃圾收集機制也很重要。
雖然說我們而不需要嚴格的去學習。但是我們至少需要知道我們的字節(jié)碼指令誰幫我們?nèi)ミ\行的,就是我們看到的字節(jié)碼指令總要有人幫我運行把。
而這個運行又分為連個維度:
- 我們java的字節(jié)碼指令,到底誰來幫我們運行呢?
- 還有調(diào)用native方法的時候誰幫我們?nèi)フ{(diào)用呢?我想必定有個native方法庫幫我們?nèi)フ{(diào)用。
所以jvm的圖解還可以去豐富:如圖:
?我們原來由上面一個區(qū)域的了解,擴展到我們還需要去了解垃圾回收,執(zhí)行引擎,他是為了執(zhí)行我們字節(jié)碼指令的。本定方法之所以能調(diào)用是因為通過本地方法接口去調(diào)用本地方法庫。這才jvm的全貌。
所以接下來我們而需要研究的區(qū)域一定是垃圾收集器。
1、垃圾回收
垃圾回收是回收整個jvm的運行時數(shù)據(jù)區(qū)嗎?是,但是我更愿意理解是堆中的,因為他更具有代表性,雖然其他區(qū)域也會有。但是主要在堆區(qū)域、。
我們堆內(nèi)存的垃圾回收不就是上面說的嗎?
那么這個區(qū)域垃圾回收我們會去怎么做?加入我們是垃圾回收設計者?
所以“”
- 第一步我的確認什么樣的對象是垃圾?
- 當確認有對象是垃圾之后,該如何回收?最起碼要有對應的回收算法吧
- 我知道這個算法之后,一定會有好事之者幫我對這個算法去進行落地。他會根據(jù)對應的垃圾收集器,讓我去實現(xiàn)這個算法,讓我去使用。所以就需要去學習垃圾收集器
- 有個各種垃圾收集器:他們的優(yōu)勢和劣勢又是什么,我該如何進行選型?
- 再者,我們學會查看垃圾回收日志文件
所以接下來就是圍著這個內(nèi)容去進行思考
所以什么樣對象是垃圾呢?
我們應該知道他有兩個維度:
- 引用計數(shù) “就是有人引用我或者指向我,我就不是垃圾,因為有人用我嘛。但是有一種情況就是,我兩個對象之間彼此互相引用”這就會形成一個循環(huán)引用。這時候他們兩個都沒有人用,但是他們彼此相互引用,所以他們又不能稱之為垃圾,所以引用計數(shù)法會存在循環(huán)引用的問題。
- 可達性分析;就是我能夠選擇出一個GC root 作為root節(jié)點,由他出發(fā),然后看卡某一個對象是否可達。就是看看是否有一個對象指向他。 這樣就比較又去一個點,就是我們看看他時候可達,前提我們看看什么樣的對象能夠成為GC ROOT。
我想,他要成為一個eGc root首先他要在java線程中能夠很長時間存在,因為他作為一個上帝的視角。他需要存在這樣一個意義,如果你沒有這樣一個意義你就不能成為gc root ,就是說你自己你自己生命周期都這么短。
所以說什么樣的對象能夠成為GC ROOT呢?
我們下節(jié)在探討。文章來源:http://www.zghlxwxcb.cn/news/detail-512019.html
JVM-類加載與運行區(qū)詳細分析(一)_平凡之路無盡路的博客-CSDN博客文章來源地址http://www.zghlxwxcb.cn/news/detail-512019.html
到了這里,關于JVM-java對象內(nèi)存分布(二)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!