更新時(shí)間:2022-12-27 12:32:58 來(lái)源:動(dòng)力節(jié)點(diǎn) 瀏覽2093次
Stop-the-World:
JVM由于要執(zhí)行GC而停止了應(yīng)用程序的執(zhí)行稱之為Stop-the-World,該情形會(huì)在任何一種GC算法中發(fā)生。當(dāng)Stop-the-world發(fā)生時(shí),除了GC所需的線程以外,所有線程都處于等待狀態(tài)直到GC任務(wù)完成。事實(shí)上,GC優(yōu)化很多時(shí)候就是指減少Stop-the-world發(fā)生的時(shí)間,從而使系統(tǒng)具有高吞吐 、低停頓 的特點(diǎn)。
(1)程序計(jì)數(shù)器
記錄當(dāng)前線程所執(zhí)行的字節(jié)碼行號(hào),用于獲取下一條執(zhí)行的字節(jié)碼。
當(dāng)多線程運(yùn)行時(shí),每個(gè)線程切換后需要知道上一次所運(yùn)行的狀態(tài)、位置。
由此也可以看出程序計(jì)數(shù)器是每個(gè)線程私有的。
(2)虛擬機(jī)棧
虛擬機(jī)棧由一個(gè)一個(gè)的棧幀組成,棧幀是在每一個(gè)方法調(diào)用時(shí)產(chǎn)生的。
每一個(gè)棧幀由局部變量區(qū)、操作數(shù)棧等組成。每創(chuàng)建一個(gè)棧幀壓棧,當(dāng)一個(gè)方法執(zhí)行完畢之后則出棧。
如果出現(xiàn)方法遞歸調(diào)用出現(xiàn)死循環(huán)的話就會(huì)造成棧幀過(guò)多,最終會(huì)拋出 StackOverflowError。
若線程執(zhí)行過(guò)程中棧幀大小超出虛擬機(jī)棧限制,則會(huì)拋出 StackOverflowError。
若虛擬機(jī)棧允許動(dòng)態(tài)擴(kuò)展,但在嘗試擴(kuò)展時(shí)內(nèi)存不足,或者在為一個(gè)新線程初始化新的虛擬機(jī)棧時(shí)申請(qǐng)不到足夠的內(nèi)存,則會(huì)拋出 OutOfMemoryError。
這塊內(nèi)存區(qū)域也是線程私有的。
(3)Java堆
Java 堆是整個(gè)虛擬機(jī)所管理的最大內(nèi)存區(qū)域,所有的對(duì)象創(chuàng)建都是在這個(gè)區(qū)域進(jìn)行內(nèi)存分配。
可利用參數(shù) -Xms -Xmx 進(jìn)行堆內(nèi)存控制。
這塊區(qū)域也是垃圾回收器重點(diǎn)管理的區(qū)域,由于大多數(shù)垃圾回收器都采用分代回收算法,所有堆內(nèi)存也分為 新生代、老年代,可以方便垃圾的準(zhǔn)確回收。
這塊內(nèi)存屬于線程共享區(qū)域。
(4)方法區(qū)
方法區(qū)主要用于存放已經(jīng)被虛擬機(jī)加載的類信息,如常量,靜態(tài)變量。 這塊區(qū)域也被稱為永久代。
可利用參數(shù) -XX:PermSize -XX:MaxPermSize 控制初始化方法區(qū)和最大方法區(qū)大小。
(5)元數(shù)據(jù)區(qū)
在 JDK1.8 中已經(jīng)移除了方法區(qū)(永久代),并使用了一個(gè)元數(shù)據(jù)區(qū)域進(jìn)行代替(Metaspace)。
默認(rèn)情況下元數(shù)據(jù)區(qū)域會(huì)根據(jù)使用情況動(dòng)態(tài)調(diào)整,避免了在 1.7 中由于加載類過(guò)多從而出現(xiàn) java.lang.OutOfMemoryError: PermGen。
但也不能無(wú)線擴(kuò)展,因此可以使用 -XX:MaxMetaspaceSize來(lái)控制最大內(nèi)存。
(6)運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池是方法區(qū)的一部分,其中存放了一些符號(hào)引用。當(dāng) new 一個(gè)對(duì)象時(shí),會(huì)檢查這個(gè)區(qū)域是否有這個(gè)符號(hào)的引用。
(7)直接內(nèi)存
直接內(nèi)存又稱為 Direct Memory(堆外內(nèi)存),它并不是由 JVM 虛擬機(jī)所管理的一塊內(nèi)存區(qū)域。
有使用過(guò) Netty 的朋友應(yīng)該對(duì)這塊并內(nèi)存不陌生,在 Netty 中所有的 IO(nio) 操作都會(huì)通過(guò) Native 函數(shù)直接分配堆外內(nèi)存。
它是通過(guò)在堆內(nèi)存中的 DirectByteBuffer 對(duì)象操作的堆外內(nèi)存,避免了堆內(nèi)存和堆外內(nèi)存來(lái)回復(fù)制交換復(fù)制,這樣的高效操作也稱為零拷貝。
既然是內(nèi)存,那也得是可以被回收的。但由于堆外內(nèi)存不直接受 JVM 管理,所以常規(guī) GC 操作并不能回收堆外內(nèi)存。它是借助于老年代產(chǎn)生的 fullGC 順便進(jìn)行回收。同時(shí)也可以顯式調(diào)用 System.gc() 方法進(jìn)行回收(前提是沒(méi)有使用 -XX:+DisableExplicitGC 參數(shù)來(lái)禁止該方法)。
值得注意的是:由于堆外內(nèi)存也是內(nèi)存,是由操作系統(tǒng)管理。如果應(yīng)用有使用堆外內(nèi)存則需要平衡虛擬機(jī)的堆內(nèi)存和堆外內(nèi)存的使用占比。避免出現(xiàn)堆外內(nèi)存溢出。
(8)常用參數(shù)
通過(guò)上圖可以直觀的查看各個(gè)區(qū)域的參數(shù)設(shè)置。
常見(jiàn)的如下:
-Xms64m 最小堆內(nèi)存 64m.
-Xmx128m 最大堆內(nèi)存 128m.
-XX:NewSize=30m 新生代初始化大小為30m.
-XX:MaxNewSize=40m 新生代最大大小為40m.
-Xss=256k 線程棧大小。
-XX:+PrintHeapAtGC 當(dāng)發(fā)生 GC 時(shí)打印內(nèi)存布局。
-XX:+HeapDumpOnOutOfMemoryError 發(fā)送內(nèi)存溢出時(shí) dump 內(nèi)存。
新生代和老年代的默認(rèn)比例為 1:2,也就是說(shuō)新生代占用 1/3的堆內(nèi)存,而老年代占用 2/3 的堆內(nèi)存。
可以通過(guò)參數(shù) -XX:NewRatio=2 來(lái)設(shè)置老年代/新生代的比例。
下圖便是 Java 對(duì)象的創(chuàng)建過(guò)程,我建議最好是能默寫(xiě)出來(lái),并且要掌握每一步在做什么。
Step1:類加載檢查
虛擬機(jī)遇到一條 new 指令時(shí),首先將去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到這個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載過(guò)、解析和初始化過(guò)。如果沒(méi)有,那必須先執(zhí)行相應(yīng)的類加載過(guò)程。
Step2:分配內(nèi)存
在類加載檢查通過(guò)后,接下來(lái)虛擬機(jī)將為新生對(duì)象分配內(nèi)存。對(duì)象所需的內(nèi)存大小在類加載完成后便可確定,為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從 Java 堆中劃分出來(lái)。分配方式有 “指針碰撞” 和 “空閑列表” 兩種,選擇那種分配方式由 Java 堆是否規(guī)整決定,而 Java 堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
內(nèi)存分配的兩種方式:(補(bǔ)充內(nèi)容,需要掌握)
選擇以上兩種方式中的哪一種,取決于 Java 堆內(nèi)存是否規(guī)整。而 Java 堆內(nèi)存是否規(guī)整,取決于 GC 收集器的算法是"標(biāo)記-清除",還是"標(biāo)記-整理"(也稱作"標(biāo)記-壓縮"),值得注意的是,復(fù)制算法內(nèi)存也是規(guī)整的
內(nèi)存分配并發(fā)問(wèn)題(補(bǔ)充內(nèi)容,需要掌握)
在創(chuàng)建對(duì)象的時(shí)候有一個(gè)很重要的問(wèn)題,就是線程安全,因?yàn)樵趯?shí)際開(kāi)發(fā)過(guò)程中,創(chuàng)建對(duì)象是很頻繁的事情,作為虛擬機(jī)來(lái)說(shuō),必須要保證線程是安全的,通常來(lái)講,虛擬機(jī)采用兩種方式來(lái)保證線程安全:
CAS+失敗重試: CAS 是樂(lè)觀鎖的一種實(shí)現(xiàn)方式。所謂樂(lè)觀鎖就是,每次不加鎖而是假設(shè)沒(méi)有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直到成功為止。虛擬機(jī)采用 CAS 配上失敗重試的方式保證更新操作的原子性。
TLAB: 為每一個(gè)線程預(yù)先在 Eden 區(qū)分配一塊兒內(nèi)存,JVM 在給線程中的對(duì)象分配內(nèi)存時(shí),首先在 TLAB 分配,當(dāng)對(duì)象大于 TLAB 中的剩余內(nèi)存或 TLAB 的內(nèi)存已用盡時(shí),再采用上述的 CAS 進(jìn)行內(nèi)存分配
Step3:初始化零值
內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭),這一步操作保證了對(duì)象的實(shí)例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問(wèn)到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。
Step4:設(shè)置對(duì)象頭
初始化零值完成之后,虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是那個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息、對(duì)象的哈希碼、對(duì)象的 GC 分代年齡等信息。 這些信息存放在對(duì)象頭中。 另外,根據(jù)虛擬機(jī)當(dāng)前運(yùn)行狀態(tài)的不同,如是否啟用偏向鎖等,對(duì)象頭會(huì)有不同的設(shè)置方式。
以上就是關(guān)于“Java數(shù)據(jù)同步解決方案”的介紹,大家如果想了解更多相關(guān)知識(shí),可以關(guān)注一下本站的Java在線學(xué)習(xí),里面的課程內(nèi)容細(xì)致全面,很適合沒(méi)有基礎(chǔ)的小伙伴學(xué)習(xí),希望對(duì)大家能夠有所幫助。
相關(guān)閱讀
0基礎(chǔ) 0學(xué)費(fèi) 15天面授
有基礎(chǔ) 直達(dá)就業(yè)
業(yè)余時(shí)間 高薪轉(zhuǎn)行
工作1~3年,加薪神器
工作3~5年,晉升架構(gòu)
提交申請(qǐng)后,顧問(wèn)老師會(huì)電話與您溝通安排學(xué)習(xí)
初級(jí) 202925
初級(jí) 203221
初級(jí) 202629
初級(jí) 203743