国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

深入探討Java內(nèi)存區(qū)域

2019-11-26 15:02:25
字體:
供稿:網(wǎng)友

一、概述
Java虛擬機(jī)在執(zhí)行Java程序的過程中會(huì)把它所管理的內(nèi)存劃分為若干不同的數(shù)據(jù)區(qū)域,這些區(qū)域都有各自的用途以及創(chuàng)建和銷毀的時(shí)間。Java虛擬機(jī)所管理的內(nèi)存將會(huì)包括以下幾個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)域,如下圖所示:

下面就每一個(gè)區(qū)域進(jìn)行闡述。

二、運(yùn)行時(shí)數(shù)據(jù)區(qū)域
程序計(jì)數(shù)器

程序計(jì)數(shù)器,可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。在虛擬機(jī)的概念模型里,字節(jié)碼解釋器工作就是通過改變程序計(jì)數(shù)器的值來選擇下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都要依賴這個(gè)計(jì)數(shù)器來完成。

多線程中,為了讓線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各條線程之間互不影響、獨(dú)立存儲(chǔ),因此這塊內(nèi)存是 線程私有 的。

當(dāng)線程正在執(zhí)行的是一個(gè)Java方法,這個(gè)計(jì)數(shù)器記錄的是在正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;當(dāng)執(zhí)行的是Native方法,這個(gè)計(jì)數(shù)器值為空。

此內(nèi)存區(qū)域是唯一一個(gè)沒有規(guī)定任何OutOfMemoryError情況的區(qū)域 。

Java虛擬機(jī)棧
Java虛擬機(jī)棧也是線程私有的 ,它的生命周期與線程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈表、方法出口信息等。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程。

局部變量表中存放了編譯器可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對(duì)象引用和returnAddress類型(指向了一條字節(jié)碼指令的地址)。

如果擴(kuò)展時(shí)無法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出OutOfMemoryError異常。

本地方法棧
本地方法棧與虛擬機(jī)的作用相似,不同之處在于虛擬機(jī)棧為虛擬機(jī)執(zhí)行的Java方法服務(wù),而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)。有的虛擬機(jī)直接把本地方法棧和虛擬機(jī)棧合二為一。

會(huì)拋出stackOverflowError和OutOfMemoryError異常。

Java堆

Java堆是所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例 。

Java堆是垃圾收集器管理的主要區(qū)域。由于現(xiàn)在收集器基本采用分代回收算法,所以Java堆還可細(xì)分為:新生代和老年代。從內(nèi)存分配的角度來看,線程共享的Java堆中可能劃分出多個(gè)線程私有的分配緩沖區(qū)(TLAB)。

Java堆可以處于物理上不連續(xù)的內(nèi)存空間,只要邏輯上連續(xù)的即可。在實(shí)現(xiàn)上,既可以實(shí)現(xiàn)固定大小的,也可以是擴(kuò)展的。

如果堆中沒有內(nèi)存完成實(shí)例分配,并且堆也無法完成擴(kuò)展時(shí),將會(huì)拋出OutOfMemoryError異常。

方法區(qū)

方法區(qū)是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù) 。

相對(duì)而言,垃圾收集行為在這個(gè)區(qū)域比較少出現(xiàn),但并非數(shù)據(jù)進(jìn)了方法區(qū)就永久的存在了,這個(gè)區(qū)域的內(nèi)存回收目標(biāo)主要是針對(duì)常量池的回收和對(duì)類型的卸載,

當(dāng)方法區(qū)無法滿足內(nèi)存分配需要時(shí),將拋出OutOfMemoryError異常。

運(yùn)行時(shí)常量池:

是方法區(qū)的一部分,它用于存放編譯期生成的各種字面量和符號(hào)引用。

直接內(nèi)存

直接內(nèi)存不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,在NIO類中引入一種基于通道與緩沖區(qū)的IO方式,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個(gè)存儲(chǔ)在Java堆中的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。

直接內(nèi)存的分配不會(huì)受到Java堆大小的限制,但是會(huì)受到本機(jī)內(nèi)存大小的限制,所有也可能會(huì)拋OutOfMemoryError異常。

三、對(duì)象的創(chuàng)建、布局和訪問過程
對(duì)象的創(chuàng)建

創(chuàng)建一個(gè)對(duì)象通常是需要new關(guān)鍵字,當(dāng)虛擬機(jī)遇到一條new指令時(shí),首先檢查這個(gè)指令的參數(shù)是否在常量池中定位到一個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載、解析和初始化過。如果那么執(zhí)行相應(yīng)的類加載過程。

類加載檢查通過后,虛擬機(jī)將為新生對(duì)象分配內(nèi)存。為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從Java堆中劃分出來。分配的方式有兩種: 一種叫 指針碰撞 ,假設(shè)Java堆中內(nèi)存是絕對(duì)規(guī)整的,用過的和空閑的內(nèi)存各在一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,分配內(nèi)存就是把那個(gè)指針向空閑空間的那邊挪動(dòng)一段與對(duì)象大小相等的距離。 另一種叫 空閑列表 :如果Java堆中的內(nèi)存不是規(guī)整的,虛擬機(jī)就需要維護(hù)一個(gè)列表,記錄哪個(gè)內(nèi)存塊是可用的,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例,并更新列表上的記錄。 采用哪種分配方式是由Java堆是否規(guī)整決定的,而Java堆是否規(guī)整是由所采用的垃圾收集器是否帶有壓縮整理功能決定的。 另 外一個(gè)需要考慮的問題就是對(duì)象創(chuàng)建時(shí)的線程安全問題,有兩種解決方案:一是對(duì)分配內(nèi)存空間的動(dòng)作進(jìn)行同步處理;另一種是吧內(nèi)存分配的動(dòng)作按照線程劃分在不 同的空間之中進(jìn)行,即每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存(TLAB),哪個(gè)線程要分配內(nèi)存就在哪個(gè)線程的TLAB上分配,只有TLAB用完并分配 新的TLAB時(shí)才需要同步鎖定。

內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間初始化為零值。這一步操作保證了對(duì)象的實(shí)例字段在Java代碼中可以不賦初始值就可以直接使用。

接下來虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息等,這些信息存放在對(duì)象的對(duì)象頭中。

上面的工作都完成以后,從虛擬機(jī)的角度來看一個(gè)新的對(duì)象已經(jīng)產(chǎn)生了。但是從Java程序的角度,還需要執(zhí)行init方法,把對(duì)象按照程序員的意愿進(jìn)行初始化,這樣一個(gè)真正可用的對(duì)象才算完全產(chǎn)生出來。

對(duì)象的內(nèi)存布局
在HotSpot虛擬機(jī)中,對(duì)象在內(nèi)存中存儲(chǔ)的布局可分為三個(gè)部分: 對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充。

對(duì)象頭包括兩個(gè)部分:第一部分用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼、GC分代年齡、線程所持有的鎖等。官方稱之為“Mark Word”。第二個(gè)部分為是類型指針,即對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例。

實(shí)例數(shù)據(jù)是對(duì)象真正存儲(chǔ)的有效信息,也是程序代碼中所定義的各種類型的字段內(nèi)容。

對(duì)齊填充并不是必然存在的,僅僅起著占位符的作用。、Hotpot VM要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍,對(duì)象頭部分正好是8字節(jié)的倍數(shù),所以當(dāng)實(shí)例數(shù)據(jù)部分沒有對(duì)齊時(shí),需要通過對(duì)齊填充來對(duì)齊。

對(duì)象的訪問定位
Java程序通過棧上的reference數(shù)據(jù)來操作堆上的具體對(duì)象。主要的訪問方式有使用句柄和直接指針兩種:

句柄:Java堆將會(huì)劃出一塊內(nèi)存來作為句柄池,引用中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息 。如圖所示:

直接指針:Java堆對(duì)象的布局要考慮如何放置訪問類型數(shù)據(jù)的相關(guān)信息,引用中存儲(chǔ)的就是對(duì)象地址 。如圖所示:

兩個(gè)方式各有優(yōu)點(diǎn),使用句柄最大的好處是引用中存儲(chǔ)的是穩(wěn)定的句柄地址,對(duì)象被移動(dòng)時(shí)只會(huì)改變句柄中實(shí)例的地址,引用不需要修改、使用直接指針訪問的好處是速度更快,它節(jié)省了一次指針定位的時(shí)間開銷。

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 鸡泽县| 吉木萨尔县| 惠州市| 关岭| 东兰县| 得荣县| 松江区| 崇仁县| 新闻| 巴里| 宾阳县| 咸丰县| 五大连池市| 鄂伦春自治旗| 洛浦县| 孟村| 阿瓦提县| 错那县| 汝城县| 文登市| 曲水县| 青浦区| 云林县| 朔州市| 百色市| 岑溪市| 沙坪坝区| 维西| 肇庆市| 麻阳| 丰城市| 霍城县| 南丹县| 高陵县| 富裕县| 嘉兴市| 台山市| 凭祥市| 延庆县| 渝北区| 虎林市|