Java JVM内存模型
簡(jiǎn)述JVM內(nèi)存模型
線程私有的運(yùn)行時(shí)數(shù)據(jù)區(qū): 程序計(jì)數(shù)器、Java 虛擬機(jī)棧、本地方法棧。
線程共享的運(yùn)行時(shí)數(shù)據(jù)區(qū):Java 堆、方法區(qū)。
簡(jiǎn)述程序計(jì)數(shù)器
程序計(jì)數(shù)器表示當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。
程序計(jì)數(shù)器不會(huì)產(chǎn)生StackOverflowError和OutOfMemoryError。
簡(jiǎn)述虛擬機(jī)棧
Java 虛擬機(jī)棧用來(lái)描述 Java 方法執(zhí)行的內(nèi)存模型。線程創(chuàng)建時(shí)就會(huì)分配一個(gè)棧空間,線程結(jié)束后棧空間被回收。
棧中元素用于支持虛擬機(jī)進(jìn)行方法調(diào)用,每個(gè)方法在執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀存儲(chǔ)方法的局部變量表、操作棧、動(dòng)態(tài)鏈接和返回地址等信息。
虛擬機(jī)棧會(huì)產(chǎn)生兩類異常:
StackOverflowError:線程請(qǐng)求的棧深度大于虛擬機(jī)允許的深度拋出。
OutOfMemoryError:如果 JVM 棧容量可以動(dòng)態(tài)擴(kuò)展,虛擬機(jī)棧占用內(nèi)存超出拋出。
簡(jiǎn)述本地方法棧
本地方法棧與虛擬機(jī)棧作用相似,不同的是虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法服務(wù),本地方法棧為本地方法服務(wù)。可以將虛擬機(jī)棧看作普通的java函數(shù)對(duì)應(yīng)的內(nèi)存模型,本地方法棧看作由native關(guān)鍵詞修飾的函數(shù)對(duì)應(yīng)的內(nèi)存模型。
本地方法棧會(huì)產(chǎn)生兩類異常:
StackOverflowError:線程請(qǐng)求的棧深度大于虛擬機(jī)允許的深度拋出。
OutOfMemoryError:如果 JVM 棧容量可以動(dòng)態(tài)擴(kuò)展,虛擬機(jī)棧占用內(nèi)存超出拋出。
簡(jiǎn)述JVM中的堆
堆主要作用是存放對(duì)象實(shí)例,Java 里幾乎所有對(duì)象實(shí)例都在堆分配內(nèi)存,堆也是內(nèi)存管理中最大的一塊。Java的垃圾回收主要就是針對(duì)堆這一區(qū)域進(jìn)行。
可通過(guò) -Xms 和 -Xmx 設(shè)置堆的最小和最大容量。
堆會(huì)拋出 OutOfMemoryError異常。
簡(jiǎn)述方法區(qū)
方法區(qū)用于存儲(chǔ)被虛擬機(jī)加載的類信息、常量、靜態(tài)變量等數(shù)據(jù)。
JDK6之前使用永久代實(shí)現(xiàn)方法區(qū),容易內(nèi)存溢出。JDK7 把放在永久代的字符串常量池、靜態(tài)變量等移出,JDK8 中拋棄永久代,改用在本地內(nèi)存中實(shí)現(xiàn)的元空間來(lái)實(shí)現(xiàn)方法區(qū),把 JDK 7 中永久代內(nèi)容移到元空間。
方法區(qū)會(huì)拋出 OutOfMemoryError異常。
簡(jiǎn)述運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池存放常量池表,用于存放編譯器生成的各種字面量與符號(hào)引用。一般除了保存 Class 文件中描述的符號(hào)引用外,還會(huì)把符號(hào)引用翻譯的直接引用也存儲(chǔ)在運(yùn)行時(shí)常量池。除此之外,也會(huì)存放字符串基本類型。
JDK8之前,放在方法區(qū),大小受限于方法區(qū)。JDK8將運(yùn)行時(shí)常量池存放堆中。
簡(jiǎn)述直接內(nèi)存
直接內(nèi)存也稱為堆外內(nèi)存,就是把內(nèi)存對(duì)象分配在JVM堆外的內(nèi)存區(qū)域。這部分內(nèi)存不是虛擬機(jī)管理,而是由操作系統(tǒng)來(lái)管理。
Java通過(guò)通過(guò)DriectByteBuffer對(duì)其進(jìn)行操作,避免了在 Java 堆和 Native堆來(lái)回復(fù)制數(shù)據(jù)。
簡(jiǎn)述java創(chuàng)建對(duì)象的過(guò)程
檢查該指令的參數(shù)能否在常量池中定位到一個(gè)類的符號(hào)引用,并檢查引用代表的類是否已被加載、解析和初始化,如果沒(méi)有就先執(zhí)行類加載。
通過(guò)檢查通過(guò)后虛擬機(jī)將為新生對(duì)象分配內(nèi)存。
完成內(nèi)存分配后虛擬機(jī)將成員變量設(shè)為零值
設(shè)置對(duì)象頭,包括哈希碼、GC 信息、鎖信息、對(duì)象所屬類的類元信息等。
執(zhí)行 init 方法,初始化成員變量,執(zhí)行實(shí)例化代碼塊,調(diào)用類的構(gòu)造方法,并把堆內(nèi)對(duì)象的首地址賦值給引用變量。
簡(jiǎn)述JVM給對(duì)象分配內(nèi)存的策略
指針碰撞: 這種方式在內(nèi)存中放一個(gè)指針作為分界指示器將使用過(guò)的內(nèi)存放在一邊,空閑的放在另一邊,通過(guò)指針挪動(dòng)完成分配。
空閑列表: 對(duì)于 Java 堆內(nèi)存不規(guī)整的情況,虛擬機(jī)必須維護(hù)一個(gè)列表記錄哪些內(nèi)存可用,在分配時(shí)從列表中找到一塊足夠大的空間劃分給對(duì)象并更新列表記錄。
java對(duì)象內(nèi)存分配是如何保證線程安全的
簡(jiǎn)述對(duì)象的內(nèi)存布局
對(duì)象在堆內(nèi)存的存儲(chǔ)布局可分為對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充。
對(duì)象頭主要包含兩部分?jǐn)?shù)據(jù): MarkWord、類型指針。MarkWord 用于存儲(chǔ)哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標(biāo)志位、線程持有的鎖、偏向線程ID等信息。
類型指針即對(duì)象指向他的類元數(shù)據(jù)指針,如果對(duì)象是一個(gè) Java 數(shù)組,會(huì)有一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù),
實(shí)例數(shù)據(jù)存儲(chǔ)代碼中所定義的各種類型的字段信息。
對(duì)齊填充起占位作用。HotSpot 虛擬機(jī)要求對(duì)象的起始地址必須是8的整數(shù)倍,因此需要對(duì)齊填充。
如何判斷對(duì)象是否是垃圾
引用計(jì)數(shù)法:設(shè)置引用計(jì)數(shù)器,對(duì)象被引用計(jì)數(shù)器加 1,引用失效時(shí)計(jì)數(shù)器減 1,如果計(jì)數(shù)器為 0 則被標(biāo)記為垃圾。會(huì)存在對(duì)象間循環(huán)引用的問(wèn)題,一般不使用這種方法。
可達(dá)性分析:通過(guò) GC Roots 的根對(duì)象作為起始節(jié)點(diǎn),從這些節(jié)點(diǎn)開(kāi)始,根據(jù)引用關(guān)系向下搜索,如果某個(gè)對(duì)象沒(méi)有被搜到,則會(huì)被標(biāo)記為垃圾。可作為 GC Roots 的對(duì)象包括虛擬機(jī)棧和本地方法棧中引用的對(duì)象、類靜態(tài)屬性引用的對(duì)象、常量引用的對(duì)象。
簡(jiǎn)述java的引用類型
強(qiáng)引用: 被強(qiáng)引用關(guān)聯(lián)的對(duì)象不會(huì)被回收。一般采用 new 方法創(chuàng)建強(qiáng)引用。
軟引用:被軟引用關(guān)聯(lián)的對(duì)象只有在內(nèi)存不夠的情況下才會(huì)被回收。一般采用 SoftReference 類來(lái)創(chuàng)建軟引用。
弱引用:垃圾收集器碰到即回收,也就是說(shuō)它只能存活到下一次垃圾回收發(fā)生之前。一般采用 WeakReference 類來(lái)創(chuàng)建弱引用。
虛引用: 無(wú)法通過(guò)該引用獲取對(duì)象。唯一目的就是為了能在對(duì)象被回收時(shí)收到一個(gè)系統(tǒng)通知。虛引用必須與引用隊(duì)列聯(lián)合使用。
簡(jiǎn)述標(biāo)記清除算法、標(biāo)記整理算法和標(biāo)記復(fù)制算法
標(biāo)記清除算法:先標(biāo)記需清除的對(duì)象,之后統(tǒng)一回收。這種方法效率不高,會(huì)產(chǎn)生大量不連續(xù)的碎片。
標(biāo)記整理算法:先標(biāo)記存活對(duì)象,然后讓所有存活對(duì)象向一端移動(dòng),之后清理端邊界以外的內(nèi)存
標(biāo)記復(fù)制算法:將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中一塊。當(dāng)使用的這塊空間用完了,就將存活對(duì)象復(fù)制到另一塊,再把已使用過(guò)的內(nèi)存空間一次清理掉。
簡(jiǎn)述分代收集算法
根據(jù)對(duì)象存活周期將內(nèi)存劃分為幾塊,不同塊采用適當(dāng)?shù)氖占惴ā?br /> 一般將堆分為新生代和老年代,對(duì)這兩塊采用不同的算法。
新生代使用:標(biāo)記復(fù)制算法
老年代使用:標(biāo)記清除或者標(biāo)記整理算法
簡(jiǎn)述Serial垃圾收集器
單線程串行收集器。垃圾回收的時(shí)候,必須暫停其他所有線程。新生代使用標(biāo)記復(fù)制算法,老年代使用標(biāo)記整理算法。簡(jiǎn)單高效。
簡(jiǎn)述ParNew垃圾收集器
可以看作Serial垃圾收集器的多線程版本,新生代使用標(biāo)記復(fù)制算法,老年代使用標(biāo)記整理算法。
簡(jiǎn)述Parallel Scavenge垃圾收集器
注重吞吐量,即cpu運(yùn)行代碼時(shí)間/cpu耗時(shí)總時(shí)間(cpu運(yùn)行代碼時(shí)間+ 垃圾回收時(shí)間)。新生代使用標(biāo)記復(fù)制算法,老年代使用標(biāo)記整理算法。
簡(jiǎn)述CMS垃圾收集器
注重最短時(shí)間停頓。CMS垃圾收集器為最早提出的并發(fā)收集器,垃圾收集線程與用戶線程同時(shí)工作。采用標(biāo)記清除算法。該收集器分為初始標(biāo)記、并發(fā)標(biāo)記、并發(fā)預(yù)清理、并發(fā)清除、并發(fā)重置這么幾個(gè)步驟。
初始標(biāo)記:暫停其他線程(stop the world),標(biāo)記與GC roots直接關(guān)聯(lián)的對(duì)象。并發(fā)標(biāo)記:可達(dá)性分析過(guò)程(程序不會(huì)停頓)。
并發(fā)預(yù)清理:查找執(zhí)行并發(fā)標(biāo)記階段從年輕代晉升到老年代的對(duì)象,重新標(biāo)記,暫停虛擬機(jī)(stop the world)掃描CMS堆中剩余對(duì)象。
并發(fā)清除:清理垃圾對(duì)象,(程序不會(huì)停頓)。
并發(fā)重置,重置CMS收集器的數(shù)據(jù)結(jié)構(gòu)。
簡(jiǎn)述G1垃圾收集器
和之前收集器不同,該垃圾收集器把堆劃分成多個(gè)大小相等的獨(dú)立區(qū)域(Region),新生代和老年代不再物理隔離。通過(guò)引入 Region 的概念,從而將原來(lái)的一整塊內(nèi)存空間劃分成多個(gè)的小空間,使得每個(gè)小空間可以單獨(dú)進(jìn)行垃圾回收。
初始標(biāo)記:標(biāo)記與GC roots直接關(guān)聯(lián)的對(duì)象。
并發(fā)標(biāo)記:可達(dá)性分析。
最終標(biāo)記,對(duì)并發(fā)標(biāo)記過(guò)程中,用戶線程修改的對(duì)象再次標(biāo)記一下。
篩選回收:對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,然后根據(jù)用戶所期望的GC停頓時(shí)間制定回收計(jì)劃并回收。
簡(jiǎn)述Minor GC
Minor GC指發(fā)生在新生代的垃圾收集,因?yàn)?Java 對(duì)象大多存活時(shí)間短,所以 Minor GC 非常頻繁,一般回收速度也比較快。
簡(jiǎn)述Full GC
Full GC 是清理整個(gè)堆空間—包括年輕代和永久代。調(diào)用System.gc(),老年代空間不足,空間分配擔(dān)保失敗,永生代空間不足會(huì)產(chǎn)生full gc。
常見(jiàn)內(nèi)存分配策略
大多數(shù)情況下對(duì)象在新生代 Eden 區(qū)分配,當(dāng) Eden 沒(méi)有足夠空間時(shí)將發(fā)起一次 Minor GC。
大對(duì)象需要大量連續(xù)內(nèi)存空間,直接進(jìn)入老年代區(qū)分配。
如果經(jīng)歷過(guò)第一次 Minor GC 仍然存活且能被 Survivor 容納,該對(duì)象就會(huì)被移動(dòng)到 Survivor 中并將年齡設(shè)置為 1,并且每熬過(guò)一次 Minor GC 年齡就加 1 ,當(dāng)增加到一定程度(默認(rèn)15)就會(huì)被晉升到老年代。
如果在 Survivor 中相同年齡所有對(duì)象大小的總和大于 Survivor 的一半,年齡不小于該年齡的對(duì)象就可以直接進(jìn)入老年代。
空間分配擔(dān)保。MinorGC 前虛擬機(jī)必須檢查老年代最大可用連續(xù)空間是否大于新生代對(duì)象總空間,如果滿足則說(shuō)明這次 Minor GC 確定安全。如果不,JVM會(huì)查看HandlePromotionFailure 參數(shù)是否允許擔(dān)保失敗,如果允許會(huì)繼續(xù)檢查老年代最大可用連續(xù)空間是否大于歷次晉升老年代對(duì)象的平均大小,如果滿足將Minor GC,否則改成一次 FullGC。
簡(jiǎn)述JVM類加載過(guò)程
加載:
驗(yàn)證:對(duì)文件格式,元數(shù)據(jù),字節(jié)碼,符號(hào)引用等驗(yàn)證正確性。
準(zhǔn)備:在方法區(qū)內(nèi)為類變量分配內(nèi)存并設(shè)置為0值。
解析:將符號(hào)引用轉(zhuǎn)化為直接引用。
初始化:執(zhí)行類構(gòu)造器clinit方法,真正初始化。
簡(jiǎn)述JVM中的類加載器
BootstrapClassLoader啟動(dòng)類加載器:加載/lib下的jar包和類。C++編寫(xiě)。
ExtensionClassLoader擴(kuò)展類加載器: /lib/ext目錄下的jar包和類。java編寫(xiě)。
AppClassLoader應(yīng)用類加載器,加載當(dāng)前classPath下的jar包和類。java編寫(xiě)。
簡(jiǎn)述雙親委派機(jī)制
一個(gè)類加載器收到類加載請(qǐng)求之后,首先判斷當(dāng)前類是否被加載過(guò)。已經(jīng)被加載的類會(huì)直接返回,如果沒(méi)有被加載,首先將類加載請(qǐng)求轉(zhuǎn)發(fā)給父類加載器,一直轉(zhuǎn)發(fā)到啟動(dòng)類加載器,只有當(dāng)父類加載器無(wú)法完成時(shí)才嘗試自己加載。
加載類順序:BootstrapClassLoader->ExtensionClassLoader->AppClassLoader->CustomClassLoader
檢查類是否加載順序:
CustomClassLoader->AppClassLoader->ExtensionClassLoader->BootstrapClassLoader
雙親委派機(jī)制的優(yōu)點(diǎn)
如何破壞雙親委派機(jī)制
重載loadClass()方法,即自定義類加載器。
如何構(gòu)建自定義類加載器
JVM常見(jiàn)調(diào)優(yōu)參數(shù)
-
-Xms 初始堆大小
-
-Xmx 最大堆大小
-
-XX:NewSize 年輕代大小
-
-XX:MaxNewSize 年輕代最大值
-
-XX:PermSize 永生代初始值
-
-XX:MaxPermSize 永生代最大值
-
-XX:NewRatio 新生代與老年代的比例
總結(jié)
以上是生活随笔為你收集整理的Java JVM内存模型的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。