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