Java虛擬機(jī)02--Java內(nèi)存區(qū)域與內(nèi)存溢出異常

?運(yùn)行時(shí)數(shù)據(jù)區(qū)域

? ? Java虛擬機(jī)在執(zhí)行程序的過(guò)程中會(huì)把它所管理的內(nèi)存劃分成若干個(gè)不同的數(shù)據(jù)區(qū)域。這些區(qū)域都有各自的用途浇衬,以及創(chuàng)建和銷(xiāo)毀的時(shí)間。有的區(qū)域隨著虛擬機(jī)進(jìn)程的啟動(dòng)而存在,有些區(qū)域則依賴用戶線程的啟動(dòng)和人結(jié)束建立而銷(xiāo)毀


Java虛擬機(jī)運(yùn)行時(shí)的數(shù)據(jù)區(qū)



程序計(jì)數(shù)器(線程隔離的數(shù)據(jù)區(qū))

? ? ? ? 程序計(jì)數(shù)器是一塊較小的內(nèi)存空間擒悬,可以看作是當(dāng)前線程執(zhí)行的字節(jié)碼的行號(hào)指示器秀鞭。

? ? ? ? 字節(jié)碼解釋器工作時(shí)就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取銷(xiāo)一條需要執(zhí)行的字節(jié)碼指令(分支趋观,循環(huán),跳轉(zhuǎn)等)锋边。

? ? ? ? Java虛擬機(jī)的一個(gè)處理器(對(duì)于多核處理器來(lái)說(shuō)是一個(gè)內(nèi)核)都只會(huì)執(zhí)行一條線程中的指令皱坛。為了線程切換之后能夠恢復(fù)到正確的執(zhí)行位置,所以每個(gè)線程需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器豆巨。這類獨(dú)立存儲(chǔ)的內(nèi)存區(qū)域?yàn)椤熬€程私有”的內(nèi)存區(qū)域剩辟。

? ? ? ? 線程執(zhí)行的是Java方法:計(jì)數(shù)器記錄正在執(zhí)行的虛擬機(jī)字節(jié)碼指令地址

? ? ? ? 線程執(zhí)行的是Native方法:計(jì)數(shù)器值為空(Undefined)

此內(nèi)存區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒(méi)有歸歸哪個(gè)任何OutOfMemoryError情況的區(qū)域。? ? ? ??


Java虛擬機(jī)棧(線程隔離的數(shù)據(jù)區(qū))

? ? ? ? 與程序計(jì)數(shù)器一樣往扔,贩猎,Java虛擬機(jī)棧(Java Virtual Machine Stacks)也是線程私有的亿乳,它的生命周期和線程相同坞嘀。

? ? ? ? Java虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同事都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲(chǔ)局部變量表、操作數(shù)棧尸变、動(dòng)態(tài)鏈接

方法出口等信息蝗罗。每個(gè)方法調(diào)用直至執(zhí)行完成的過(guò)程艇棕,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)中入棧到出棧的過(guò)程。

? ? ? ? ?大體上可以將Java內(nèi)存區(qū)別分為堆內(nèi)存(Heap)和棧內(nèi)存(Stack)串塑,但是實(shí)際劃分關(guān)系遠(yuǎn)比這復(fù)雜沼琉。這里的棧主要講的就是虛擬機(jī)棧中的局部變量表部分。

? ? ? ? ?局部變量表中存放了編譯器可知的各種基本數(shù)據(jù)類型(Java 8大基本數(shù)據(jù)類型)拟赊、對(duì)象引用類型 (可能是一個(gè)?指向?qū)ο笃鹗嫉刂??的引用指針刺桃,也可能是指向一個(gè)代表對(duì)象的句柄? 或其他與此對(duì)象相關(guān)的位置)

? ? ? ? 在Java虛擬機(jī)規(guī)范中,對(duì)這個(gè)區(qū)域規(guī)定了兩種異常狀況:如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverFlow異常瑟慈;如果虛擬機(jī)可以動(dòng)態(tài)擴(kuò)展(當(dāng)前大部分的虛擬機(jī)都可以動(dòng)態(tài)擴(kuò)展桃移,只不過(guò)是Java虛擬機(jī)規(guī)范中也允許固定長(zhǎng)度的虛擬機(jī)棧),如果擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存葛碧,就會(huì)拋出OutOfMemoryError異常


本地方法棧(線程隔離的數(shù)據(jù)區(qū))

? ??????本地方法棧(Native Method Stack)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的借杰,它們之間的區(qū)別不過(guò)是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)进泼。在虛擬機(jī)規(guī)范中對(duì)本地方法棧中方法使用的語(yǔ)言蔗衡、使用方式與數(shù)據(jù)結(jié)構(gòu)并沒(méi)有強(qiáng)制規(guī)定,因此具體的虛擬機(jī)可以自由實(shí)現(xiàn)它乳绕。甚至有的虛擬機(jī)(譬如Sun HotSpot虛擬機(jī))直接就把本地方法棧和虛擬機(jī)棧合二為一绞惦。與虛擬機(jī)棧一樣,本地方法棧區(qū)域也會(huì)拋出StackOverflowError和OutOfMemoryError異常


Java堆(由所有線程共享的數(shù)據(jù)區(qū))

? ??????Java堆(Java Heap)是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊洋措。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域济蝉,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例菠发,幾乎所有的對(duì)象實(shí)例都在這分配內(nèi)存王滤。(Java虛擬機(jī)規(guī)范中是這樣描述的:所有的對(duì)象實(shí)例以及數(shù)組都要在堆上分配[1],但是隨著JIT(Just-In-Time Compiler--即時(shí)編譯器)的發(fā)展與逃逸分析技術(shù)逐漸成熟滓鸠,棧上分配雁乡、標(biāo)量替換[2]優(yōu)化技術(shù)將會(huì)導(dǎo)致一些微妙的變化發(fā)生,所有的對(duì)象都分配在堆上也漸漸變得不是那么“絕對(duì)”了糜俗。)

? ??????Java堆是垃圾收集器管理的主要區(qū)域踱稍,因此很多時(shí)候也被稱做“GC堆”(Garbage Collected Heap)。

? ? ? ? ? ? ? ? 1.從回收的角度來(lái)看悠抹,由于現(xiàn)在收集器基本上都基本采用分代收集算法寞射,所以Java堆中還可以細(xì)分為:新生代和老年代;再細(xì)致一點(diǎn)的有?

? ? ? ? ? ? ? ? ? ? ? ? Eden空間锌钮、From Survivor空間桥温、To Survivor空間等。

? ? ? ? ? ? ? ? 2.從內(nèi)存分配的角度來(lái)看梁丘,線程共享的Java堆中可能劃分出多個(gè)線程私有的分配緩沖區(qū)(Thread Local Allocation Buffer,TLAB)

? ??????無(wú)論如何劃分侵浸,都與存放內(nèi)容無(wú)關(guān),無(wú)論哪個(gè)區(qū)域氛谜,存儲(chǔ)的都仍然是對(duì)象實(shí)例掏觉,進(jìn)一步劃分的目的是為了更好地回收內(nèi)存,或者更快地分配內(nèi)存值漫。

? ??????根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定澳腹,Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可,就像我們的磁盤(pán)空間一樣酱塔。在實(shí)現(xiàn)時(shí)沥邻,既可以實(shí)現(xiàn)成固定大小的,也可以是可擴(kuò)展的羊娃,不過(guò)當(dāng)前主流的虛擬機(jī)都是按照可擴(kuò)展來(lái)實(shí)現(xiàn)的(通過(guò)-Xmx【最大】和-Xms【最小】控制)唐全。如果在堆中沒(méi)有內(nèi)存完成實(shí)例分配,并且堆也無(wú)法再擴(kuò)展時(shí)蕊玷,將會(huì)拋出OutOfMemoryError異常邮利。


方法區(qū)(由所有線程共享的數(shù)據(jù)區(qū))

? ? ? ? 用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量垃帅、靜態(tài)變量延届、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分贸诚,但是它卻有一個(gè)別名叫做Non-Heap(非堆)祷愉,目的應(yīng)該是與Java堆區(qū)分開(kāi)來(lái)。對(duì)于習(xí)慣在HotSpot(SunJDK和OpenJDK中所帶的虛擬機(jī))虛擬機(jī)上開(kāi)發(fā)赦颇、部署程序的開(kāi)發(fā)者來(lái)說(shuō),很多人都更愿意把方法區(qū)稱為“永久代”(Permanent Generation)赴涵,本質(zhì)上兩者并不等價(jià)媒怯,僅僅是因?yàn)镠otSpot虛擬機(jī)的設(shè)計(jì)團(tuán)隊(duì)選擇把GC分代收集擴(kuò)展至方法區(qū),或者說(shuō)使用永久代來(lái)實(shí)現(xiàn)方法區(qū)而已髓窜,這樣HotSpot的垃圾收集器可以像管理Java堆一樣管理這部分內(nèi)存扇苞,能夠省去專門(mén)為方法區(qū)編寫(xiě)內(nèi)存管理代碼的工作。對(duì)于其他虛擬機(jī)(如BEA JRockit寄纵、IBM J9等)來(lái)說(shuō)是不存在永久代的概念的鳖敷。

? ? ? ? 原則上,如何實(shí)現(xiàn)方法區(qū)屬于虛擬機(jī)實(shí)現(xiàn)細(xì)節(jié)程拭,不受虛擬機(jī)規(guī)范約束定踱,但使用永久代來(lái)實(shí)現(xiàn)方法區(qū),現(xiàn)在看來(lái)并不是一個(gè)好主意恃鞋,因?yàn)檫@樣更容易遇到內(nèi)存溢出問(wèn)題永久代有-XX:MaxPermSize的上限崖媚,J9和JRockit只要沒(méi)有觸碰到進(jìn)程可用內(nèi)存的上限,例如32位系統(tǒng)中的4GB恤浪,就不會(huì)出現(xiàn)問(wèn)題)畅哑,而且有極少數(shù)方法(例如String.intern())會(huì)因這個(gè)原因?qū)е虏煌摂M機(jī)下有不同的表現(xiàn)。因此水由,對(duì)于HotSpot虛擬機(jī)荠呐,根據(jù)官方發(fā)布的路線圖信息,現(xiàn)在也有放棄永久代并逐步改為采用Native Memory來(lái)實(shí)現(xiàn)方法區(qū)的規(guī)劃了[1],在目前已經(jīng)發(fā)布的JDK 1.7的HotSpot中泥张,已經(jīng)把原本放在永久代的字符串常量池移出呵恢。

String.intern()

? ? ? ? 直接使用雙引號(hào)聲明出來(lái)的String對(duì)象會(huì)直接存儲(chǔ)在常量池中。

? ? ? ? 如果不是用雙引號(hào)聲明的String對(duì)象圾结,可以使用String提供的intern方法瑰剃。intern 方法會(huì)從字符串常量池中查詢當(dāng)前字符串是否存在,若不存在就會(huì)將當(dāng)前字符串放入常量池中


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

? ??????????運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分筝野。Class文件中除了有類的版本晌姚、字段、方法歇竟、接口等描述信息外挥唠,還有一項(xiàng)信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號(hào)引用焕议,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放

? ? ? ? Java虛擬機(jī)對(duì)Class文件每一部分(自然也包括常量池)的格式都有嚴(yán)格規(guī)定宝磨,每一個(gè)字節(jié)用于存儲(chǔ)哪種數(shù)據(jù)都必須符合規(guī)范上的要求才會(huì)被虛擬機(jī)認(rèn)可、裝載和執(zhí)行盅安,但對(duì)于運(yùn)行時(shí)常量池唤锉,Java虛擬機(jī)規(guī)范沒(méi)有做任何細(xì)節(jié)的要求,不同的提供商實(shí)現(xiàn)的虛擬機(jī)可以按照自己的需要來(lái)實(shí)現(xiàn)這個(gè)內(nèi)存區(qū)域别瞭。不過(guò)窿祥,一般來(lái)說(shuō),除了保存Class文件中描述的符號(hào)引用外蝙寨,還會(huì)把翻譯出來(lái)的直接引用也存儲(chǔ)在運(yùn)行時(shí)常量池中晒衩。

? ? ? ? ?當(dāng)常量池?zé)o法申請(qǐng)到內(nèi)存時(shí)會(huì)拋出OutOfMemoryError異常。




直接內(nèi)存

? ? ? ? 直接內(nèi)存(Direct Memory)并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分墙歪,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域听系。但是這部分內(nèi)存也被頻繁地使用,而且也可能導(dǎo)致OutOfMemoryError異常出現(xiàn)虹菲。

? ? ? ?在JDK 1.4中新加入了NIO(New Input/Output)類靠胜,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O方式,它可以使用Native函數(shù)庫(kù)直接分配堆外內(nèi)存毕源,然后通過(guò)一個(gè)存儲(chǔ)在Java堆中的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作髓帽。這樣能在一些場(chǎng)景中顯著提高性能,因?yàn)楸苊饬嗽贘ava堆和Native堆中來(lái)回復(fù)制數(shù)據(jù)脑豹。

? ? ? ? 本機(jī)的直接內(nèi)存分配是不會(huì)受到Java堆 大小的限制郑藏,但是還是會(huì)受到本機(jī)總內(nèi)存的(包括RAM以及SWAP區(qū)或者分頁(yè)文件)大小以及處理器尋址空間的限制。服務(wù)器管理員在配置虛擬機(jī)參數(shù)時(shí)瘩欺,會(huì)根據(jù)實(shí)際內(nèi)存設(shè)置-Xmx等參數(shù)信息必盖,但經(jīng)常忽略直接內(nèi)存拌牲,使得各個(gè)內(nèi)存區(qū)域總和大于物理內(nèi)存限制(包括物理的和操作系統(tǒng)級(jí)的限制),從而導(dǎo)致動(dòng)態(tài)擴(kuò)展時(shí)出現(xiàn)OutOfMemoryError異常歌粥。



對(duì)象的創(chuàng)建

? ? ? ?虛擬機(jī)遇到一條new指令時(shí)塌忽,首先將去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載失驶、解析和初始化過(guò)土居。如果沒(méi)有,那必須先執(zhí)行相應(yīng)的類加載過(guò)程.

? ? ? ?在類加載檢查通過(guò)后嬉探,接下來(lái)虛擬機(jī)將為新生對(duì)象分配內(nèi)存擦耀。對(duì)象所需內(nèi)存的大小在類加載完成后便可完全確定。為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從Java堆中劃分出來(lái)涩堤。假設(shè)Java堆中內(nèi)存是絕對(duì)規(guī)整的眷蜓,所有用過(guò)的內(nèi)存都放在一邊,空閑的內(nèi)存放在另一邊胎围,中間放著一個(gè)指針作為分界點(diǎn)的指示器吁系,那所分配內(nèi)存就僅僅是把那個(gè)指針向空閑空間那邊挪動(dòng)一段與對(duì)象大小相等的距離,這種分配方式稱為“指針碰撞”(Bump the Pointer)白魂。如果Java堆中的內(nèi)存并不是規(guī)整的汽纤,已使用的內(nèi)存和空閑的內(nèi)存相互交錯(cuò),那就沒(méi)有辦法簡(jiǎn)單地進(jìn)行指針碰撞了福荸,虛擬機(jī)就必須維護(hù)一個(gè)列表蕴坪,記錄上哪些內(nèi)存塊是可用的,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例逞姿,并更新列表上的記錄,這種分配方式稱為“空閑列表”(Free List)捆等。選擇哪種分配方式由Java堆是否規(guī)整決定滞造,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。因此栋烤,在使用Serial谒养、ParNew等帶Compact過(guò)程的收集器時(shí),系統(tǒng)采用的分配算法是指針碰撞明郭,而使用CMS這種基于Mark-Sweep算法的收集器時(shí)买窟,通常采用空閑列表。

? ? ? ? ?除如何劃分可用空間之外薯定,還有另外一個(gè)需要考慮的問(wèn)題是對(duì)象創(chuàng)建在虛擬機(jī)中是非常頻繁的行為始绍,即使是僅僅修改一個(gè)指針指向的位置,在并發(fā)情況下也并不是線程安全的(可能出現(xiàn)正在給對(duì)象A分配內(nèi)存话侄,指針還沒(méi)來(lái)得及修改亏推,對(duì)象B又同時(shí)使用了原來(lái)的指針來(lái)分配內(nèi)存的情況)学赛。解決這個(gè)問(wèn)題有兩種方案,一種是對(duì)分配內(nèi)存空間的動(dòng)作進(jìn)行同步處理--實(shí)際上虛擬機(jī)采用CAS(Compare And Swap)配上失敗重試的方式保證更新操作的原子性吞杭;另一種是把內(nèi)存分配的動(dòng)作按照線程劃分在不同的空間之中進(jìn)行盏浇,即每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存,稱為本地線程分配緩沖(Thread Local Allocation Buffer,TLAB)芽狗。哪個(gè)線程要分配內(nèi)存绢掰,就在哪個(gè)線程的TLAB上分配,只有TLAB用完并分配新的TLAB時(shí)童擎,才需要同步鎖定滴劲。虛擬機(jī)是否使用TLAB可以通過(guò)-XX:+/-UseTLAB參數(shù)來(lái)設(shè)定。

? ? ? ? ?內(nèi)存分配完成后柔昼,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭)哑芹,如果使用TLAB,這一工作過(guò)程也可以提前至TLAB分配時(shí)進(jìn)行捕透。這一步操作保證了對(duì)象的實(shí)例字段在Java代碼中可以不賦初始值就直接使用聪姿,程序能訪問(wèn)到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。

? ? ? ? ?接下來(lái)乙嘀,虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置末购,例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息虎谢、對(duì)象的哈希碼盟榴、對(duì)象的GC分代年齡等信息。這些信息存放在對(duì)象的對(duì)象頭(Object Header)之中婴噩。根據(jù)虛擬機(jī)當(dāng)前的運(yùn)行狀態(tài)的不同擎场,如是否啟用偏向鎖等,對(duì)象頭會(huì)有不同的設(shè)置方式几莽。




對(duì)象的內(nèi)存布局

? ? ? ? ?HotSpot虛擬機(jī)中迅办,對(duì)象在內(nèi)存中存儲(chǔ)的布局可以分為三塊區(qū)域:對(duì)象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)和對(duì)齊填充(Padding)章蚣。

Java中的對(duì)象頭

? ? ? ? ? ?HotSpot虛擬機(jī)的對(duì)象頭包括兩部分信息站欺,第一部分用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼(HashCode)纤垂、GC分代年齡矾策、鎖狀態(tài)標(biāo)志、線程持有的鎖峭沦、偏向線程ID贾虽、偏向時(shí)間戳等,這部分?jǐn)?shù)據(jù)的長(zhǎng)度在32位和64位的虛擬機(jī)(未開(kāi)啟壓縮指針)中分別為32bit和64bit吼鱼,官方稱它為“Mark Word”榄鉴。對(duì)象需要存儲(chǔ)的運(yùn)行時(shí)數(shù)據(jù)很多履磨,其實(shí)已經(jīng)超出了32位、64位Bitmap結(jié)構(gòu)所能記錄的限度庆尘,但是對(duì)象頭信息是與對(duì)象自身定義的數(shù)據(jù)無(wú)關(guān)的額外存儲(chǔ)成本剃诅,考慮到虛擬機(jī)的空間效率,Mark Word被設(shè)計(jì)成一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小的空間內(nèi)存儲(chǔ)盡量多的信息驶忌,它會(huì)根據(jù)對(duì)象的狀態(tài)復(fù)用自己的存儲(chǔ)空間矛辕。例如,在32位的HotSpot虛擬機(jī)中付魔,如果對(duì)象處于未被鎖定的狀態(tài)下聊品,那么Mark Word的32bit空間中的25bit用于存儲(chǔ)對(duì)象哈希碼,4bit用于存儲(chǔ)對(duì)象分代年齡几苍,2bit用于存儲(chǔ)鎖標(biāo)志位翻屈,1bit固定為0,而在其他狀態(tài)(輕量級(jí)鎖定妻坝、重量級(jí)鎖定伸眶、GC標(biāo)記、可偏向)


HotSpot虛擬機(jī)對(duì)象MarkDown

? ???????對(duì)象頭的另外一部分是類型指針刽宪,即對(duì)象指向它的類元數(shù)據(jù)的指針厘贼,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例。并不是所有的虛擬機(jī)實(shí)現(xiàn)都必須在對(duì)象數(shù)據(jù)上保留類型指針圣拄,換句話說(shuō)嘴秸,查找對(duì)象的元數(shù)據(jù)信息并不一定要經(jīng)過(guò)對(duì)象本身。另外庇谆,如果對(duì)象是一個(gè)Java數(shù)組岳掐,那在對(duì)象頭中還必須有一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù),因?yàn)樘摂M機(jī)可以通過(guò)普通Java對(duì)象的元數(shù)據(jù)信息確定Java對(duì)象的大小饭耳,但是從數(shù)組的元數(shù)據(jù)中卻無(wú)法確定數(shù)組的大小串述。



對(duì)象的訪問(wèn)定位? ? ? ??

? ? ? ? 建立對(duì)象是為了使用對(duì)象,我們的Java程序需要通過(guò)棧上的reference數(shù)據(jù)來(lái)操作堆上的具體對(duì)象哥攘。由于reference類型在Java虛擬機(jī)規(guī)范中只規(guī)定了一個(gè)指向?qū)ο蟮囊闷驶停](méi)有定義這個(gè)引用應(yīng)該通過(guò)何種方式去定位材鹦、訪問(wèn)堆中的對(duì)象的具體位置逝淹,所以對(duì)象訪問(wèn)方式也是取決于虛擬機(jī)實(shí)現(xiàn)而定的。目前主流的訪問(wèn)方式有使用句柄和直接指針兩種桶唐。

? ??????如果使用句柄訪問(wèn)的話栅葡,那么Java堆中將會(huì)劃分出一塊內(nèi)存來(lái)作為句柄池,reference中存儲(chǔ)的就是對(duì)象的句柄地址尤泽,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息


通過(guò)句柄訪問(wèn)對(duì)象

? ??????如果使用直接指針訪問(wèn)欣簇,那么Java堆對(duì)象的布局中就必須考慮如何放置訪問(wèn)類型數(shù)據(jù)的相關(guān)信息规脸,而reference中存儲(chǔ)的直接就是對(duì)象地址


通過(guò)直接指針訪問(wèn)對(duì)象

? ??????這兩種對(duì)象訪問(wèn)方式各有優(yōu)勢(shì),使用句柄來(lái)訪問(wèn)的最大好處就是reference中存儲(chǔ)的是穩(wěn)定的句柄地址熊咽,在對(duì)象被移動(dòng)(垃圾收集時(shí)移動(dòng)對(duì)象是非常普遍的行為)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針莫鸭,而reference本身不需要修改

? ??????使用直接指針訪問(wèn)方式的最大好處就是速度更快横殴,它節(jié)省了一次指針定位的時(shí)間開(kāi)銷(xiāo)被因,由于對(duì)象的訪問(wèn)在Java中非常頻繁,因此這類開(kāi)銷(xiāo)積少成多后也是一項(xiàng)非成缆兀可觀的執(zhí)行成本梨与。就虛擬機(jī)Sun HotSpot而言,它是使用第二種方式進(jìn)行對(duì)象訪問(wèn)的文狱,但從整個(gè)軟件開(kāi)發(fā)的范圍來(lái)看粥鞋,各種語(yǔ)言和框架使用句柄來(lái)訪問(wèn)的情況也十分常見(jiàn)。

? ? ? ? ? ? 簡(jiǎn)單理解就是一個(gè)是指針一個(gè)是指針的指針瞄崇,一個(gè)相對(duì)速度更快呻粹,一個(gè)相對(duì)更加安全,穩(wěn)定



實(shí)戰(zhàn):OutOfMemoryError異常

?IDEA中JVM參數(shù)配置

/**

* VM Options

* -Xms20m

* -Xmx20m

* -XX:+HeapDumpOnOutOfMemoryError

* @author panghu

*/

public class HeapOOM {

public static void main(String[] args) {

List list =new ArrayList<>();

? ? ? ? while (true) {

list.add(new HeapOOM.OOMObject());

? ? ? ? }

}

static class OOMObject {

}

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末杠袱,一起剝皮案震驚了整個(gè)濱河市尚猿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌楣富,老刑警劉巖凿掂,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異纹蝴,居然都是意外死亡庄萎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)塘安,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)糠涛,“玉大人,你說(shuō)我怎么就攤上這事兼犯∪碳瘢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵切黔,是天一觀的道長(zhǎng)砸脊。 經(jīng)常有香客問(wèn)我,道長(zhǎng)纬霞,這世上最難降的妖魔是什么凌埂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮诗芜,結(jié)果婚禮上瞳抓,老公的妹妹穿的比我還像新娘埃疫。我一直安慰自己,他們只是感情好孩哑,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布栓霜。 她就那樣靜靜地躺著,像睡著了一般横蜒。 火紅的嫁衣襯著肌膚如雪叙淌。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天愁铺,我揣著相機(jī)與錄音鹰霍,去河邊找鬼。 笑死茵乱,一個(gè)胖子當(dāng)著我的面吹牛茂洒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瓶竭,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼督勺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了斤贰?” 一聲冷哼從身側(cè)響起智哀,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荧恍,沒(méi)想到半個(gè)月后瓷叫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡送巡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年摹菠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骗爆。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡次氨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出摘投,到底是詐尸還是另有隱情煮寡,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布犀呼,位于F島的核電站幸撕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏圆凰。R本人自食惡果不足惜杈帐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一体箕、第九天 我趴在偏房一處隱蔽的房頂上張望专钉。 院中可真熱鬧挑童,春花似錦、人聲如沸跃须。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)菇民。三九已至尽楔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間第练,已是汗流浹背阔馋。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留娇掏,地道東北人呕寝。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像婴梧,于是被迫代替她去往敵國(guó)和親下梢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351