這篇文章主要介紹JVM內(nèi)存結(jié)構(gòu)劃分的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
10余年的海東網(wǎng)站建設(shè)經(jīng)驗,針對設(shè)計、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。成都全網(wǎng)營銷的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整海東建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計,從而大程度地提升瀏覽體驗。創(chuàng)新互聯(lián)建站從事“海東網(wǎng)站設(shè)計”,“海東網(wǎng)站推廣”以來,每個客戶項目都認真落實執(zhí)行。
數(shù)據(jù)區(qū)域劃分
運行時內(nèi)存區(qū)域劃分:程序計數(shù)器、虛擬機棧、本地方法棧、堆、方法區(qū)
程序計數(shù)器
線程私有
通過寄存器實現(xiàn)
不會存在運行溢出
當前線程所執(zhí)行的行號指示器,記住下一條JVM指令的執(zhí)行地址
虛擬機棧
垃圾回收不涉及棧內(nèi)存
棧內(nèi)存是線程私有的,可以理解為線程運行需要的內(nèi)存空間
棧由棧幀組成,每個棧幀代表一個方法執(zhí)行時需要的內(nèi)存(參數(shù),局部變量,返回地址)
每個線程只能有一個活動棧幀,對應(yīng)著當前正在執(zhí)行的那個方法
棧內(nèi)存分配過大只能支撐一定的遞歸調(diào)用,并不會影響運行速度,還可能減少線程數(shù)量(因為物理內(nèi)存是一定的)
本地方法棧
為運行本地方法時分配的內(nèi)存(HotSpot把虛擬機棧和本地方法棧合二為一了)
堆
有垃圾回收機制
線程共享,需要考慮線程安全問題
存儲的都是對象的實例(通過new關(guān)鍵字創(chuàng)建的對象)
從內(nèi)存分配的角度來說:堆中可以劃分出多個線程私有的分配緩沖區(qū)(TLAB),以提升對象分配時的效率
Java堆可以處于物理上不連續(xù)的內(nèi)存空間,但在邏輯上應(yīng)該視為連續(xù)的(但是對于比如數(shù)組這種大對象,可能會要求連續(xù)的內(nèi)存空間)
方法區(qū)
線程共享區(qū)
存儲已被虛擬機加載的類型信息,常量,靜態(tài)變量,即時編譯器編譯后的代碼緩存
在虛擬機啟動時被創(chuàng)建,邏輯上屬于堆的一部分(不同JVM實現(xiàn)的方式不同)
JDK1.6使用永久代(PerGen)作為方法區(qū)的實現(xiàn)
JDK1.8使用元空間(Metaspace)對方法區(qū)進行實現(xiàn)(包含Class ClassLoader 常量池三個部分,放在直接內(nèi)存中)StringTable放在堆中(有助于垃圾回收管理)
使用場景:如Spring Mybatis使用的動態(tài)加載
運行時常量池
運行時常量池是方法區(qū)的一部分
二進制字節(jié)碼內(nèi)容:類基本信息\常量池表\類方法定義,包含了虛擬機指令
其中,常量池表中存放編譯期間生成的各種字面量(比如各種基本數(shù)據(jù)類型)與符號引用(比如,類名\方法名\參數(shù)類型),這部分內(nèi)容將在類加載后存放到方法區(qū)的運行時常量池中,并把符號地址變?yōu)檎鎸嵉刂?/p>
StringTable
類似于hashTable結(jié)構(gòu),不能自動擴容
常量池中的字符串只是符號,第一次使用時才變?yōu)閷ο?/p>
利用串池機制,避免重復(fù)創(chuàng)建字符對象
案例
字符串拼接的原理是編譯期優(yōu)化
字符串拼接原理是StringBuilder(JDK1.8)
使用intern()方法,主動將串池中還沒有的字符串對象放入串池
// StringTable [ "a", "b" ,"ab" ] hashtable 結(jié)構(gòu),不能擴容 public class Demo1_22 { // 常量池中的信息,都會被加載到運行時常量池中, 這時 a b ab 都是常量池中的符號,還沒有變?yōu)?nbsp;java 字符串對象 // ldc #2 會把 a 符號變?yōu)?nbsp;"a" 字符串對象 // ldc #3 會把 b 符號變?yōu)?nbsp;"b" 字符串對象 // ldc #4 會把 ab 符號變?yōu)?nbsp;"ab" 字符串對象 public static void main(String[] args) { String s1 = "a"; // 懶惰的 String s2 = "b"; String s3 = "ab"; String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString() new String("ab") String s5 = "a" + "b"; // javac 在編譯期間的優(yōu)化,結(jié)果已經(jīng)在編譯期確定為ab System.out.println(s3 == s5); } }
JDK1.7以后,利用intern()方法,會將字符串對象嘗試放入串池,如果有則并不會放入,如果沒有則放入串池, 會把串池中的對象返回;而JDK1.6調(diào)用intern()方法,是將對象拷貝一份到串池中,指向堆中的對象本身引用并不不改變
public class Demo1_23 { // ["ab", "a", "b"] public static void main(String[] args) { demo1(); demo2(); } static void demo1() { // 串池中事前沒有"ab",intern()之后,s返回的是串池中的對象 String s = new String("a") + new String("b"); String s1 = s.intern(); System.out.println(s == "ab"); // true System.out.println(s1 == "ab"); //true } static void demo2() { // 串池中事前已有"ab",s返回的仍是堆中的對象 String x = "ab"; String s = new String("a") + new String("b"); // 堆 new String("a") new String("b") new String("ab") String s2 = s.intern(); // 將這個字符串對象嘗試放入串池,如果有則并不會放入,如果沒有則放入串池, 會把串池中的對象返回 System.out.println( s2 == x); // true System.out.println( s == x ); //false } }
位置
JDK1.6時,StringTable放在元空間內(nèi),屬于永久代的位置,但是StringTable占用內(nèi)存容易觸發(fā)full gc耗時較久;JDK1.7以后將StringTable放在堆內(nèi)存中,隨著內(nèi)存占用增大首先觸發(fā)minor gc,耗時較短.
直接內(nèi)存
使用Native函數(shù)直接分配堆外內(nèi)存,然后通過Java堆里的DirectByteBuffer對象作為引用對這塊內(nèi)存的引用進行操作.
原理說明:
使用Unsafe對象完成直接內(nèi)存的分配和回收,回收時需要主動調(diào)用freeMemory方法
ByteBuffer的實現(xiàn)類內(nèi)部使用了Cleaner(虛引用)來監(jiān)測ByteBuffer(BB)對象,一旦BB對象被垃圾回收,會有ReferenceHandler線程通過Cleaner方法調(diào)用freeMemory來釋放內(nèi)存
創(chuàng)建新對象說明
HotSpot虛擬機在Java堆中對象分配、布局和訪問的過程
對象的創(chuàng)建
new字節(jié)碼指令
虛擬機遇到new字節(jié)碼指令時,首先檢查能否在常量池中定位到一個類的符號引用,并檢查該符號引用的來是否已被加載、解析和初始化。如果沒有,則執(zhí)行相應(yīng)的類加載過程。
類加載檢查后,虛擬機為新生對象分配內(nèi)存
內(nèi)存分配
對象所需的內(nèi)存大小在類加載過程中可以確定,在Java虛擬機中為對象劃分內(nèi)存時有兩種方式:指針碰撞、空閑列表
指針碰撞: 利用一個指針作為已用內(nèi)存和未用內(nèi)存的分界點的指示器,內(nèi)存分配就僅僅是指針的移動。優(yōu)點在于不會造成內(nèi)存碎片化,但是速度較慢
空閑列表:虛擬機維護一個內(nèi)存使用記錄表,使用時,從空閑的內(nèi)存區(qū)域直接劃分一塊足夠大的空間給對象實例。
內(nèi)存分配的線程安全問題
劃分可用空間后仍要考慮并發(fā)情況下對內(nèi)存的使用,有兩種方式解決內(nèi)存沖突的問題:CAS配上失敗重試、TLAB本地線程分配緩沖
TLAB:把內(nèi)存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在Java堆中預(yù)先分配了一小塊內(nèi)存空間
對象的內(nèi)存布局
對象在堆內(nèi)存中的布局可以劃分為三個部分:對象頭、實例數(shù)據(jù)、對齊填充
對象頭
對象頭中包含兩類信息:Mark Word、類型指針
Mark Word
存儲對象自身運行時數(shù)據(jù),考慮到虛擬機的空間效率,被設(shè)計成一個動態(tài)定義的數(shù)據(jù)結(jié)構(gòu),即根據(jù)對象的狀態(tài)復(fù)用自己的存儲空間(數(shù)據(jù)長度在32位和64位虛擬機上分別為32個比特和64個比特)
類型指針
對象中指向它類型元數(shù)據(jù)的指針,Java虛擬機通過這個指針來確定該對象是哪個類的實例(不是所有虛擬機都必須在對象數(shù)據(jù)上保留類型指針)此外,如果對象是一個數(shù)組,對象頭中還必須擁有一塊記錄數(shù)據(jù)長度的數(shù)據(jù)
實例數(shù)據(jù)
即程序代碼里定義的各種類型的字段內(nèi)容,包括從父類繼承的或子類中定義的字段。各類數(shù)據(jù)存儲是按照一定順序的(long/double、ints...),而寬度相同的字段總是被分配到一起存放,所以父類中定義的變量可能會出現(xiàn)在子類之前。
對齊填充
占位符,無特殊意義
HotSpot虛擬機的自動內(nèi)存管理系統(tǒng)要求對象的起始地址必須是8字節(jié)的整倍數(shù),若有些對象的對象頭和示例數(shù)據(jù)內(nèi)存設(shè)計不是8的倍數(shù),則需要利用占位符來進行填充。
對象的訪問定位
Java程序通過reference數(shù)據(jù)操作對上的具體對象,主流的訪問方式有兩種:句柄、直接指針
句柄
Java堆中可能劃分出一塊內(nèi)存作為句柄池。reference中存儲對象的句柄地址,句柄中包含對象的實例數(shù)據(jù)和類型數(shù)據(jù)的具體地址信息。
直接指針
Java堆中對象的布局需要考慮如何放置類型數(shù)據(jù)的相關(guān)信息(如訪問信息)。reference中存儲的直接就是對象地址,如果只訪問對象本身,就不要多一次間接訪問的開銷
優(yōu)缺點
使用句柄訪問, reference數(shù)據(jù)只需關(guān)乎句柄地址,當對象被回收或移動后只需改變句柄中的實例數(shù)據(jù)指針,而reference本身不用修改
使用直接指針省去了一次指針定位的時間開銷,速度更快,由于Java中對象的訪問相當頻繁,所以效果可觀。
HotSpot使用直接指針的方式
以上是“JVM內(nèi)存結(jié)構(gòu)劃分的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
分享名稱:JVM內(nèi)存結(jié)構(gòu)劃分的示例分析
本文網(wǎng)址:http://jinyejixie.com/article28/iicicp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營銷型網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計公司、企業(yè)網(wǎng)站制作、小程序開發(fā)、App設(shè)計、建站公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)