摘要:三對(duì)象的內(nèi)存布局對(duì)象在堆中的布局分為三個(gè)區(qū)域?qū)ο箢^,實(shí)例數(shù)據(jù),對(duì)齊填充??偨Y(jié)了解內(nèi)存區(qū)域是對(duì)的深入學(xué)習(xí),以前只知道有堆和棧的區(qū)分,現(xiàn)在我們了解到了具體的堆棧的作用。
引言
學(xué)習(xí)Java也有一段時(shí)間了,總感覺(jué)有些東西學(xué)的不是很精通。例如Java內(nèi)存區(qū)域到底是怎么樣的?程序是怎么跑的?對(duì)象是怎么存放的?這些都影響了我對(duì)自己的程序運(yùn)行的熟悉程度。
一. 運(yùn)行時(shí)數(shù)據(jù)區(qū)域Java虛擬機(jī)在執(zhí)行java程序的過(guò)程中,會(huì)把它所管理的內(nèi)存劃分成若干個(gè)不同的數(shù)據(jù)區(qū)域(每當(dāng)運(yùn)行一個(gè)java程序都會(huì)啟動(dòng)一個(gè)虛擬機(jī))。有一本書(shū)叫做《Java虛擬機(jī)規(guī)范》,講述了Sun公司對(duì)Java虛擬機(jī)實(shí)現(xiàn)的相關(guān)規(guī)范,其中講了虛擬機(jī)將所管理的內(nèi)存分為以下幾個(gè)部分:
程序計(jì)數(shù)器
虛擬機(jī)棧
本地方法區(qū)
堆
方法區(qū)
其中方法區(qū)和堆是由所有線程共享的,例如使用ThreadPoolExecutor創(chuàng)建多個(gè)線程時(shí),堆與方法區(qū)都可以被多個(gè)線程讀取。
程序計(jì)數(shù)器 學(xué)過(guò)計(jì)算機(jī)組成原理的人都會(huì)知道在CPU的寄存器中有一個(gè)PC寄存器,存放下一條指令地址,這里,虛擬機(jī)不使用CPU的程序計(jì)數(shù)器,自己在內(nèi)存中設(shè)立一片區(qū)域來(lái)模擬CPU的程序計(jì)數(shù)器。只有一個(gè)程序計(jì)數(shù)器是不夠的,當(dāng)多個(gè)線程切換執(zhí)行時(shí),那就單個(gè)程序計(jì)數(shù)器就沒(méi)辦法了,虛擬機(jī)規(guī)范中指出,每一條線程都有一個(gè)獨(dú)立的程序計(jì)數(shù)器。注意,Java虛擬機(jī)中的程序計(jì)數(shù)器指向正在執(zhí)行的字節(jié)碼地址,而不是下一條。
虛擬機(jī)棧 是線程私有的,它的生命周期與線程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀(我覺(jué)得可以把它看作是一個(gè)快照,記錄下進(jìn)入方法前的一些參數(shù),實(shí)際上是方法運(yùn)行時(shí)的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)),用于存放局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出口等信息。每一個(gè)方法從調(diào)用直到執(zhí)行完成的過(guò)程都對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)中的入棧到出棧的過(guò)程。我們平時(shí)把內(nèi)存分為堆內(nèi)存和棧內(nèi)存,其中的棧內(nèi)存就指的是虛擬機(jī)棧的局部變量表部分。局部變量表存放了編譯期可以知道的基本數(shù)據(jù)類型,對(duì)象引用,和返回后所指向的字節(jié)碼的地址。
本地方法區(qū) 與 虛擬機(jī)棧 所發(fā)揮的作用很類似,但是要注意一下,虛擬機(jī)規(guī)范中沒(méi)有對(duì)本地方法區(qū)中的方法作強(qiáng)制規(guī)定,虛擬機(jī)可以自由實(shí)現(xiàn),即可以不是字節(jié)碼。但是也可以是字節(jié)碼,這樣虛擬機(jī)棧和本地方法區(qū)就可以合二為一,事實(shí)上,OpenJDK和SunJDK所自帶的HotSpot虛擬機(jī)就直接將虛擬機(jī)棧和本地方法區(qū)合二為一。
堆 這個(gè)概念應(yīng)該很多人都很熟悉,例如初學(xué)C語(yǔ)言的時(shí)候,老師就會(huì)講malloc方法會(huì)在堆中分配空間,這里也一樣。這個(gè)區(qū)域是用來(lái)存放對(duì)象實(shí)例的,幾乎所有對(duì)象實(shí)例都會(huì)在這里分配內(nèi)存,虛擬機(jī)規(guī)范中講:所有對(duì)象的實(shí)例以及數(shù)組都要在堆上分配。但是隨著JIT(Just-in-time) 編譯期的發(fā)展,有些時(shí)候也有可能在棧上分配(這里我也不是很明白其中的道理)。堆是java垃圾收集器管理的主要區(qū)域(很多時(shí)候會(huì)稱為GC堆,不叫垃圾堆),垃圾收集器實(shí)現(xiàn)了對(duì)象的自動(dòng)銷毀。
方法區(qū) 也是各個(gè)線程共享的區(qū)域,它用于存儲(chǔ)已經(jīng)被虛擬機(jī)加載過(guò)的類信息,常量,靜態(tài)變量,及時(shí)編譯期編譯后的代碼(類方法)等數(shù)據(jù)。這里要講一下運(yùn)行時(shí)常量池,它是方法區(qū)的一部分,用于存放編譯期生成的各種字面量和符號(hào)引用(其實(shí)就是八大基本類型的包裝類型和String類型數(shù)據(jù))。
二. 對(duì)象的創(chuàng)建最后還有一個(gè)直接內(nèi)存,在JDK1.4版本中加入了NIO類,引入了基于通道(Channel)與緩沖區(qū)(Buffer)的I/O方式,也就是說(shuō)通過(guò)這種方式,不會(huì)在運(yùn)行時(shí)數(shù)據(jù)區(qū)域分配內(nèi)存,這樣就避免了在運(yùn)行時(shí)數(shù)據(jù)區(qū)域來(lái)回復(fù)制數(shù)據(jù),直接調(diào)用外部?jī)?nèi)存。
對(duì)于面向?qū)ο蟮囊婚T(mén)語(yǔ)言,我們無(wú)時(shí)不在通過(guò)new關(guān)鍵字創(chuàng)建對(duì)象,那么這個(gè)過(guò)程又是怎樣的呢?
當(dāng)虛擬機(jī)遇到一條new指令的時(shí)候,首先會(huì)去檢查所new的類是否已經(jīng)被加載,在哪里檢查?當(dāng)然在方法區(qū),方法區(qū)存放了加載過(guò)的類信息。如果沒(méi)有加載,那么先執(zhí)行類的加載。
通過(guò)類加載檢查后,虛擬機(jī)開(kāi)始為新生對(duì)象分配內(nèi)存,對(duì)象所需要的內(nèi)存大小在類加載完成后已經(jīng)可以確定,這時(shí)候只要在堆中分配空間即可。分配內(nèi)存有兩種方式,第一種,我們假設(shè)內(nèi)存絕對(duì)規(guī)整,那么只要在用過(guò)的內(nèi)存和沒(méi)用過(guò)的內(nèi)存間放置一個(gè)指針即可,每次分配空間的時(shí)候只要把指針向空閑空間移動(dòng)相應(yīng)距離即可。第二種,我們假設(shè)空閑內(nèi)存和非空閑內(nèi)存夾雜在一起,實(shí)際上就是這種情況,那么就需要一個(gè)列表,去記錄堆內(nèi)存的使用情況,操作系統(tǒng)對(duì)內(nèi)存的管理就是這樣的。
那么,我們還要考慮一個(gè)問(wèn)題,即在多線程的情況下,只有一個(gè)指針怎么能確保一個(gè)線程分配了內(nèi)存指針沒(méi)修改的時(shí)候另一個(gè)線程又分配內(nèi)存不會(huì)覆蓋之前的內(nèi)存呢?這里有一種方法,讓每一個(gè)線程在堆中先預(yù)分配一小塊內(nèi)存(TLAB本地線程分配緩沖),每個(gè)線程只在自己的內(nèi)存中分配內(nèi)存。
最后,對(duì)象被成功分配內(nèi)存。我們知道通過(guò)一個(gè)對(duì)象,我們可以通過(guò)getClass()方法獲取類,默認(rèn)比較兩個(gè)對(duì)象實(shí)際比較的是對(duì)象內(nèi)存的哈希值,這又是怎么實(shí)現(xiàn)的呢?其實(shí)在分配完內(nèi)存后,虛擬機(jī)會(huì)對(duì)對(duì)象進(jìn)行必要的設(shè)置,對(duì)象的類,對(duì)象的哈希碼等信息都存放在對(duì)象的對(duì)象頭中,所以分配的內(nèi)存大小絕不止屬性的總和。
三. 對(duì)象的內(nèi)存布局對(duì)象在堆中的布局分為三個(gè)區(qū)域:對(duì)象頭,實(shí)例數(shù)據(jù),對(duì)齊填充。
對(duì)象頭 包括兩個(gè)部分,第一部分用于存儲(chǔ)自身運(yùn)行時(shí)的數(shù)據(jù)例如GC標(biāo)志位,MonirGC次數(shù),哈希碼,鎖狀態(tài),哪個(gè)線程可以擁有等被稱為MarkWord(標(biāo)記字)。第二部分存放指向方法區(qū)類數(shù)據(jù)的指針。在32位系統(tǒng)中,class指針大小為4字節(jié),標(biāo)記字大小為4字節(jié)。在64位系統(tǒng)中標(biāo)記字大小為8字節(jié)。
實(shí)例數(shù)據(jù) 存放類的屬性信息,包括父類的屬性信息。數(shù)組的實(shí)例部分還包括數(shù)組的長(zhǎng)度。實(shí)例信息按類分別4字節(jié)對(duì)齊。
對(duì)齊填充 這是虛擬機(jī)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍,可以說(shuō)對(duì)齊填充沒(méi)有什么特別的含義。
四. 對(duì)象的訪問(wèn)定位我們知道,引用是引用,對(duì)象實(shí)例是對(duì)象實(shí)例。引用存放在虛擬機(jī)棧中,數(shù)據(jù)類型為reference,對(duì)象實(shí)例存放在堆中。那么引用是如何指向?qū)ο髮?shí)例的呢?
主流的訪問(wèn)方式有兩種,第一種是通過(guò)句柄池,如果使用句柄池,那么java堆中將會(huì)劃分出一部分內(nèi)存作為句柄池,句柄包含對(duì)象類型指針指向方法區(qū)的類型信息,還有對(duì)象實(shí)例指針,指向堆中的實(shí)例地址。
第二種是reference引用直接指向堆中的對(duì)象實(shí)例,對(duì)象實(shí)例的對(duì)象頭存放對(duì)象類型指針。
兩種方法各有優(yōu)勢(shì),第一種可以在對(duì)象實(shí)例在GC時(shí)移動(dòng)的時(shí)候只改變句柄池中的對(duì)象實(shí)例指針,而不用改變r(jià)eference引用本身。第二種方法就是訪問(wèn)速度快,減少了一次指針定位的時(shí)間開(kāi)銷。目前HotSpot虛擬機(jī)就采用的第二種方式。
總結(jié)了解java內(nèi)存區(qū)域是對(duì)java的深入學(xué)習(xí),以前只知道有堆和棧的區(qū)分,現(xiàn)在我們了解到了具體的堆棧的作用。內(nèi)存是怎么劃分的,對(duì)象是怎么存儲(chǔ)的,方法和屬性的存放區(qū)別。通過(guò)對(duì)這些內(nèi)容的了解,會(huì)讓我們寫(xiě)java程序更加游刃有余,有的放矢。
更多文章:http://blog.gavinzh.com
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/64374.html
摘要:一內(nèi)存區(qū)域虛擬機(jī)在運(yùn)行時(shí),會(huì)把內(nèi)存空間分為若干個(gè)區(qū)域,根據(jù)虛擬機(jī)規(guī)范版的規(guī)定,虛擬機(jī)所管理的內(nèi)存區(qū)域分為如下部分方法區(qū)堆內(nèi)存虛擬機(jī)棧本地方法棧程序計(jì)數(shù)器。前言 在JVM的管控下,Java程序員不再需要管理內(nèi)存的分配與釋放,這和在C和C++的世界是完全不一樣的。所以,在JVM的幫助下,Java程序員很少會(huì)關(guān)注內(nèi)存泄露和內(nèi)存溢出的問(wèn)題。但是,一旦JVM發(fā)生這些情況的時(shí)候,如果你不清楚JVM內(nèi)存的...
摘要:虛擬機(jī)包括一套字節(jié)碼指令集一組寄存器一個(gè)棧一個(gè)垃圾回收堆和一個(gè)存儲(chǔ)方法域。而使用虛擬機(jī)是實(shí)現(xiàn)這一特點(diǎn)的關(guān)鍵。虛擬機(jī)在執(zhí)行字節(jié)碼時(shí),把字節(jié)碼解釋成具體平臺(tái)上的機(jī)器指令執(zhí)行。此內(nèi)存區(qū)域是唯一一個(gè)在虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何情況的區(qū)域。 1、 什么是JVM? JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫(xiě),JVM是一種用于計(jì)算設(shè)備的規(guī)范,它是一個(gè)虛構(gòu)出來(lái)的計(jì)算機(jī),...
摘要:虛擬機(jī)棧區(qū)也就是通常所說(shuō)的棧區(qū),它描述的是方法執(zhí)行的內(nèi)存模型,每個(gè)方法被執(zhí)行的時(shí)候都創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量表操作數(shù)棧動(dòng)態(tài)鏈接方法出口等。每個(gè)方法被調(diào)用到完成,相當(dāng)于一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程。 大多數(shù)情況下我們對(duì)GC的了解都只是淺層含義上的,下面我們來(lái)詳細(xì)講解下內(nèi)部的一些實(shí)現(xiàn)原理。講解GC之前,我們得先了解下JVM的內(nèi)存結(jié)構(gòu),才能讓我們理解GC導(dǎo)致是干嘛的。 一.J...
摘要:編譯參見(jiàn)深入理解虛擬機(jī)節(jié)走進(jìn)之一自己編譯源碼內(nèi)存模型運(yùn)行時(shí)數(shù)據(jù)區(qū)域根據(jù)虛擬機(jī)規(guī)范的規(guī)定,的內(nèi)存包括以下幾個(gè)運(yùn)運(yùn)行時(shí)數(shù)據(jù)區(qū)域程序計(jì)數(shù)器程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,他可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。 點(diǎn)擊進(jìn)入我的博客 1.1 基礎(chǔ)知識(shí) 1.1.1 一些基本概念 JDK(Java Development Kit):Java語(yǔ)言、Java虛擬機(jī)、Java API類庫(kù)JRE(...
摘要:再附一部分架構(gòu)面試視頻講解本文已被開(kāi)源項(xiàng)目學(xué)習(xí)筆記總結(jié)移動(dòng)架構(gòu)視頻大廠面試真題項(xiàng)目實(shí)戰(zhàn)源碼收錄 Java反射(一)Java反射(二)Java反射(三)Java注解Java IO(一)Java IO(二)RandomAccessFileJava NIOJava異常詳解Java抽象類和接口的區(qū)別Java深拷貝和淺拷...
閱讀 3812·2023-04-26 02:07
閱讀 3684·2021-10-27 14:14
閱讀 2871·2021-10-14 09:49
閱讀 1635·2019-08-30 15:43
閱讀 2628·2019-08-29 18:33
閱讀 2380·2019-08-29 17:01
閱讀 924·2019-08-29 15:11
閱讀 601·2019-08-29 11:06