一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

JVM內(nèi)存模型

 月影曉風(fēng) 2017-03-07

JVM定義了若干個程序執(zhí)行期間使用的數(shù)據(jù)區(qū)域。這個區(qū)域里的一些數(shù)據(jù)在JVM啟動的時(shí)候創(chuàng)建,在JVM退出的時(shí)候銷毀。而其他的數(shù)據(jù)依賴于每一個線程,在線程創(chuàng)建時(shí)創(chuàng)建,在線程退出時(shí)銷毀。


程序計(jì)數(shù)器

程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計(jì)數(shù)器來完成。

由于Java 虛擬機(jī)的多線程是通過線程輪流切換并分配處理器執(zhí)行時(shí)間的方式來實(shí)現(xiàn)的,在任何一個確定的時(shí)刻,一個處理器(對于多核處理器來說是一個內(nèi)核)只會執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個獨(dú)立的程序計(jì)數(shù)器,各條線程之間的計(jì)數(shù)器互不影響,獨(dú)立存儲,我們稱這類內(nèi)存區(qū)域?yàn)椤?strong>線程私有”的內(nèi)存。

如果線程正在執(zhí)行的是一個Java 方法,這個計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果正在執(zhí)行的是Natvie 方法,這個計(jì)數(shù)器值則為空(Undefined)。

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


虛擬機(jī)棧

線程私有,它的生命周期與線程相同。虛擬機(jī)棧描述的是Java 方法執(zhí)行的內(nèi)存模型:每個方法被執(zhí)行的時(shí)候都會同時(shí)創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作棧、動態(tài)鏈接、方法出口等信息。

動畫是由一幀一幀圖片連續(xù)切換結(jié)果的結(jié)果而產(chǎn)生的,其實(shí)虛擬機(jī)的運(yùn)行和動畫也類似,每個在虛擬機(jī)中運(yùn)行的程序也是由許多的幀的切換產(chǎn)生的結(jié)果,只是這些幀里面存放的是方法的局部變量,操作數(shù)棧,動態(tài)鏈接,方法返回地址和一些額外的附加信息組成。每一個方法被調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機(jī)棧中從入棧到出棧的過程。

 

對于執(zhí)行引擎來說,活動線程中,只有棧頂?shù)臈怯行У?,稱為當(dāng)前棧幀,這個棧幀所關(guān)聯(lián)的方法稱為當(dāng)前方法。執(zhí)行引擎所運(yùn)行的所有字節(jié)碼指令都只針對當(dāng)前棧幀進(jìn)行操作

局部變量表

局部變量表是一組變量值存儲空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。在Java程序被編譯成Class文件時(shí),就在方法的Code屬性的max_locals數(shù)據(jù)項(xiàng)中確定了該方法所需要分配的最大局部變量表的容量。

局部變量表的容量以變量槽(Slot)為最小單位,32位虛擬機(jī)中一個Slot可以存放一個32位以內(nèi)的數(shù)據(jù)類型(boolean、byte、char、short、int、float、reference和returnAddress八種)。

reference類型虛擬機(jī)規(guī)范沒有明確說明它的長度,但一般來說,虛擬機(jī)實(shí)現(xiàn)至少都應(yīng)當(dāng)能從此引用中直接或者間接地查找到對象在Java堆中的起始地址索引和方法區(qū)中的對象類型數(shù)據(jù)。

returnAddress類型是為字節(jié)碼指令jsr、jsr_w和ret服務(wù)的,它指向了一條字節(jié)碼指令的地址。

虛擬機(jī)是使用局部變量表完成參數(shù)值到參數(shù)變量列表的傳遞過程的,如果是實(shí)例方法(非static),那么局部變量表的第0位索引的Slot默認(rèn)是用于傳遞方法所屬對象實(shí)例的引用,在方法中通過this訪問。

 Slot是可以重用的,當(dāng)Slot中的變量超出了作用域,那么下一次分配Slot的時(shí)候,將會覆蓋原來的數(shù)據(jù)。Slot對對象的引用會影響GC(要是被引用,將不會被回收)。

 系統(tǒng)不會為局部變量賦予初始值(實(shí)例變量和類變量都會被賦予初始值)。也就是說不存在類變量那樣的準(zhǔn)備階段。

操作數(shù)棧

和局部變量區(qū)一樣,操作數(shù)棧也是被組織成一個以字長為單位的數(shù)組。但是和前者不同的是,它不是通過索引來訪問,而是通過標(biāo)準(zhǔn)的棧操作——壓棧和出?!獊碓L問的。比如,如果某個指令把一個值壓入到操作數(shù)棧中,稍后另一個指令就可以彈出這個值來使用。

虛擬機(jī)在操作數(shù)棧中存儲數(shù)據(jù)的方式和在局部變量區(qū)中是一樣的:如int、long、float、double、reference和returnType的存儲。對于byte、short以及char類型的值在壓入到操作數(shù)棧之前,也會被轉(zhuǎn)換為int。

虛擬機(jī)把操作數(shù)棧作為它的工作區(qū)——大多數(shù)指令都要從這里彈出數(shù)據(jù),執(zhí)行運(yùn)算,然后把結(jié)果壓回操作數(shù)棧。比如,iadd指令就要從操作數(shù)棧中彈出兩個整數(shù),執(zhí)行加法運(yùn)算,其結(jié)果又壓回到操作數(shù)棧中,看看下面的示例,它演示了虛擬機(jī)是如何把兩個int類型的局部變量相加,再把結(jié)果保存到第三個局部變量的:

  1. begin  
  2. iload_0    // push the int in local variable 0 ontothe stack  
  3. iload_1    //push the int in local variable 1 onto the stack  
  4. iadd       // pop two ints, add them, push result  
  5. istore_2   // pop int, store into local variable 2  
  6. end  
 

在這個字節(jié)碼序列里,前兩個指令iload_0和iload_1將存儲在局部變量中索引為0和1的整數(shù)壓入操作數(shù)棧中,其后iadd指令從操作數(shù)棧中彈出那兩個整數(shù)相加,再將結(jié)果壓入操作數(shù)棧。第四條指令istore_2則從操作數(shù)棧中彈出結(jié)果,并把它存儲到局部變量區(qū)索引為2的位置。下圖詳細(xì)表述了這個過程中局部變量和操作數(shù)棧的狀態(tài)變化,圖中沒有使用的局部變量區(qū)和操作數(shù)棧區(qū)域以空白表示。

  

動態(tài)連接

虛擬機(jī)運(yùn)行的時(shí)候,運(yùn)行時(shí)常量池會保存大量的符號引用,這些符號引用可以看成是每個方法的間接引用。如果代表?xiàng)珹的方法想調(diào)用代表?xiàng)珺的方法,那么這個虛擬機(jī)的方法調(diào)用指令就會以B方法的符號引用作為參數(shù),但是因?yàn)榉栆貌⒉皇侵苯又赶虼鞡方法的內(nèi)存位置,所以在調(diào)用之前還必須要將符號引用轉(zhuǎn)換為直接引用,然后通過直接引用才可以訪問到真正的方法。

如果符號引用是在類加載階段或者第一次使用的時(shí)候轉(zhuǎn)化為直接應(yīng)用,那么這種轉(zhuǎn)換成為靜態(tài)解析,如果是在運(yùn)行期間轉(zhuǎn)換為直接引用,那么這種轉(zhuǎn)換就成為動態(tài)連接。

返回地址

       方法的返回分為兩種情況,一種是正常退出,退出后會根據(jù)方法的定義來決定是否要傳返回值給上層的調(diào)用者,一種是異常導(dǎo)致的方法結(jié)束,這種情況是不會傳返回值給上層的調(diào)用方法。

不過無論是那種方式的方法結(jié)束,在退出當(dāng)前方法時(shí)都會跳轉(zhuǎn)到當(dāng)前方法被調(diào)用的位置,如果方法是正常退出的,則調(diào)用者的PC計(jì)數(shù)器的值就可以作為返回地址,,果是因?yàn)楫惓M顺龅?,則是需要通過異常處理表來確定。

方法的的一次調(diào)用就對應(yīng)著棧幀在虛擬機(jī)棧中的一次入棧出棧操作,因此方法退出時(shí)可能做的事情包括:恢復(fù)上層方法的局部變量表以及操作數(shù)棧,如果有返回值的話,就把返回值壓入到調(diào)用者棧幀的操作數(shù)棧中,還會把PC計(jì)數(shù)器的值調(diào)整為方法調(diào)用入口的下一條指令。

  

異常

在Java 虛擬機(jī)規(guī)范中,對虛擬機(jī)棧規(guī)定了兩種異常狀況:如果線程請求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError 異常;如果虛擬機(jī)棧可以動態(tài)擴(kuò)展(當(dāng)前大部分的Java 虛擬機(jī)都可動態(tài)擴(kuò)展,只不過Java 虛擬機(jī)規(guī)范中也允許固定長度的虛擬機(jī)棧),當(dāng)擴(kuò)展時(shí)無法申請到足夠的內(nèi)存時(shí)會拋出OutOfMemoryError 異常。


本地方法棧

本地方法棧(Native MethodStacks)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,其區(qū)別不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java 方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機(jī)使用到的Native 方法服務(wù)。虛擬機(jī)規(guī)范中對本地方法棧中的方法使用的語言、使用方式與數(shù)據(jù)結(jié)構(gòu)并沒有強(qiáng)制規(guī)定,因此具體的虛擬機(jī)可以自由實(shí)現(xiàn)它。甚至有的虛擬機(jī)(譬如Sun HotSpot 虛擬機(jī))直接就把本地方法棧和虛擬機(jī)棧合二為一。

與虛擬機(jī)棧一樣,本地方法棧區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常。


堆是Java 虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java 堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時(shí)創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實(shí)例,幾乎所有的對象實(shí)例都在這里分配內(nèi)存。但是隨著JIT 編譯器的發(fā)展與逃逸分析技術(shù)的逐漸成熟,棧上分配、標(biāo)量替換優(yōu)化技術(shù)將會導(dǎo)致一些微妙的變化發(fā)生,所有的對象都分配在堆上也漸漸變得不是那么“絕對”了。

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

堆的大小可以通過-Xms(最小值)和-Xmx(最大值)參數(shù)設(shè)置,-Xms為JVM啟動時(shí)申請的最小內(nèi)存,默認(rèn)為操作系統(tǒng)物理內(nèi)存的1/64但小于1G,-Xmx為JVM可申請的最大內(nèi)存,默認(rèn)為物理內(nèi)存的1/4但小于1G,默認(rèn)當(dāng)空余堆內(nèi)存小于40%時(shí),JVM會增大Heap到-Xmx指定的大小,可通過-XX:MinHeapFreeRation=來指定這個比列;當(dāng)空余堆內(nèi)存大于70%時(shí),JVM會減小heap的大小到-Xms指定的大小,可通過XX:MaxHeapFreeRation=來指定這個比列,對于運(yùn)行系統(tǒng),為避免在運(yùn)行時(shí)頻繁調(diào)整Heap的大小,通常-Xms與-Xmx的值設(shè)成一樣。

 

如果從內(nèi)存回收的角度看,由于現(xiàn)在收集器基本都是采用的分代收集算法,所以Java 堆中還可以細(xì)分為:新生代和老年代;

新生代程序新創(chuàng)建的對象都是從新生代分配內(nèi)存,新生代由Eden Space和兩塊相同大小的Survivor Space(通常又稱S0和S1或From和To)構(gòu)成,可通過-Xmn參數(shù)來指定新生代的大小,也可以通過-XX:SurvivorRation來調(diào)整Eden Space及SurvivorSpace的大小。

老年代:用于存放經(jīng)過多次新生代GC仍然存活的對象,例如緩存對象,新建的對象也有可能直接進(jìn)入老年代,主要有兩種情況:1、大對象,可通過啟動參數(shù)設(shè)置-XX:PretenureSizeThreshold=1024(單位為字節(jié),默認(rèn)為0)來代表超過多大時(shí)就不在新生代分配,而是直接在老年代分配。2、大的數(shù)組對象,且數(shù)組中無引用外部對象。

老年代所占的內(nèi)存大小為-Xmx對應(yīng)的值減去-Xmn對應(yīng)的值。

 

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


方法區(qū)

方法區(qū)在一個jvm實(shí)例的內(nèi)部,類型信息被存儲在一個稱為方法區(qū)的內(nèi)存邏輯區(qū)中。類型信息是由類加載器在類加載時(shí)從類文件中提取出來的。類(靜態(tài))變量也存儲在方法區(qū)中。

簡單說方法區(qū)用來存儲類型的元數(shù)據(jù)信息,一個.class文件是類被java虛擬機(jī)使用之前的表現(xiàn)形式,一旦這個類要被使用,java虛擬機(jī)就會對其進(jìn)行裝載、連接(驗(yàn)證、準(zhǔn)備、解析)和初始化。而裝載(后的結(jié)果就是由.class文件轉(zhuǎn)變?yōu)榉椒▍^(qū)中的一段特定的數(shù)據(jù)結(jié)構(gòu)。這個數(shù)據(jù)結(jié)構(gòu)會存儲如下信息:

 

類型信息

      這個類型的全限定名

      這個類型的直接超類的全限定名

      這個類型是類類型還是接口類型

      這個類型的訪問修飾符

      任何直接超接口的全限定名的有序列表

 

字段信息

      字段名

      字段類型

      字段的修飾符

 

方法信息

      方法名

      方法返回類型

      方法參數(shù)的數(shù)量和類型(按照順序)

      方法的修飾符

 

其他信息

      除了常量以外的所有類(靜態(tài))變量

      一個指向ClassLoader的指針

      一個指向Class對象的指針

      常量池(常量數(shù)據(jù)以及對其他類型的符號引用)

 

JVM為每個已加載的類型都維護(hù)一個常量池。常量池就是這個類型用到的常量的一個有序集合,包括實(shí)際的常量(string,integer,和floating point常量)和對類型,域和方法的符號引用。池中的數(shù)據(jù)項(xiàng)象數(shù)組項(xiàng)一樣,是通過索引訪問的。

 

每個類的這些元數(shù)據(jù),無論是在構(gòu)建這個類的實(shí)例還是調(diào)用這個類某個對象的方法,都會訪問方法區(qū)的這些元數(shù)據(jù)。

構(gòu)建一個對象時(shí),JVM會在堆中給對象分配空間,這些空間用來存儲當(dāng)前對象實(shí)例屬性以及其父類的實(shí)例屬性(而這些屬性信息都是從方法區(qū)獲得),注意,這里并不是僅僅為當(dāng)前對象的實(shí)例屬性分配空間,還需要給父類的實(shí)例屬性分配,到此其實(shí)我們就可以回答第一個問題了,即實(shí)例化父類的某個子類時(shí),JVM也會同時(shí)構(gòu)建父類的一個對象。從另外一個角度也可以印證這個問題:調(diào)用當(dāng)前類的構(gòu)造方法時(shí),首先會調(diào)用其父類的構(gòu)造方法直到Object,而構(gòu)造方法的調(diào)用意味著實(shí)例的創(chuàng)建,所以子類實(shí)例化時(shí),父類肯定也會被實(shí)例化。

類變量被類的所有實(shí)例共享,即使沒有類實(shí)例時(shí)你也可以訪問它。這些變量只與類相關(guān),所以在方法區(qū)中,它們成為類數(shù)據(jù)在邏輯上的一部分。在JVM使用一個類之前,它必須在方法區(qū)中為每個non-final類變量分配空間。

 

方法區(qū)主要有以下幾個特點(diǎn):

1、方法區(qū)是線程安全的。由于所有的線程都共享方法區(qū),所以,方法區(qū)里的數(shù)據(jù)訪問必須被設(shè)計(jì)成線程安全的。例如,假如同時(shí)有兩個線程都企圖訪問方法區(qū)中的同一個類,而這個類還沒有被裝入JVM,那么只允許一個線程去裝載它,而其它線程必須等待

2、方法區(qū)的大小不必是固定的,JVM可根據(jù)應(yīng)用需要動態(tài)調(diào)整。同時(shí),方法區(qū)也不一定是連續(xù)的,方法區(qū)可以在一個堆(甚至是JVM自己的堆)中自由分配。

3、方法區(qū)也可被垃圾收集,當(dāng)某個類不在被使用(不可觸及)時(shí),JVM將卸載這個類,進(jìn)行垃圾收集

 

可以通過-XX:PermSize-XX:MaxPermSize 參數(shù)限制方法區(qū)的大小。

對于習(xí)慣在HotSpot 虛擬機(jī)上開發(fā)和部署程序的開發(fā)者來說,很多人愿意把方法區(qū)稱為“永久代”(PermanentGeneration),本質(zhì)上兩者并不等價(jià),僅僅是因?yàn)镠otSpot 虛擬機(jī)的設(shè)計(jì)團(tuán)隊(duì)選擇把GC 分代收集擴(kuò)展至方法區(qū),或者說使用永久代來實(shí)現(xiàn)方法區(qū)而已。對于其他虛擬機(jī)(如BEA JRockit、IBM J9 等)來說是不存在永久代的概念的。

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

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


總結(jié)

名稱

特征

作用

配置參數(shù)

異常

程序計(jì)數(shù)器

占用內(nèi)存小,線程私有,

生命周期與線程相同

大致為字節(jié)碼行號指示器

虛擬機(jī)棧

線程私有,生命周期與線程相同,使用連續(xù)的內(nèi)存空間

Java 方法執(zhí)行的內(nèi)存模型,存儲局部變量表、操作棧、動態(tài)鏈接、方法出口等信息

-Xss

StackOverflowError

OutOfMemoryError

java堆

線程共享,生命周期與虛擬機(jī)相同,可以不使用連續(xù)的內(nèi)存地址

保存對象實(shí)例,所有對象實(shí)例(包括數(shù)組)都要在堆上分配

-Xms

-Xsx

-Xmn

OutOfMemoryError

方法區(qū)

線程共享,生命周期與虛擬機(jī)相同,可以不使用連續(xù)的內(nèi)存地址

存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)

-XX:PermSize:

16M

-XX:MaxPermSize

64M

OutOfMemoryError

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

方法區(qū)的一部分,具有動態(tài)性

存放字面量及符號引用

 

 


直接內(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(NewInput/Output)類,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O 方式,它可以使用Native 函數(shù)庫直接分配堆外內(nèi)存,然后通過一個存儲在Java 堆里面的DirectByteBuffer 對象作為這塊內(nèi)存的引用進(jìn)行操作。這樣能在一些場景中顯著提高性能,因?yàn)楸苊饬嗽贘ava 堆和Native 堆中來回復(fù)制數(shù)據(jù)。


堆與棧的對比

經(jīng)常有人把Java 內(nèi)存區(qū)分為堆內(nèi)存(Heap)和棧內(nèi)存(Stack),這種分法比較粗糙,Java內(nèi)存區(qū)域的劃分實(shí)際上遠(yuǎn)比這復(fù)雜。這種劃分方式的流行只能說明大多數(shù)程序員最關(guān)注的、與對象內(nèi)存分配關(guān)系最密切的內(nèi)存區(qū)域是這兩塊。

堆很靈活,但是不安全。對于對象,我們要動態(tài)地創(chuàng)建、銷毀,不能說后創(chuàng)建的對象沒有銷毀,先前創(chuàng)建的對象就不能銷毀,那樣的話我們的程序就寸步難行,所以Java中用堆來存儲對象。而一旦堆中的對象被銷毀,我們繼續(xù)引用這個對象的話,就會出現(xiàn)著名的 NullPointerException,這就是堆的缺點(diǎn)——錯誤的引用邏輯只有在運(yùn)行時(shí)才會被發(fā)現(xiàn)。

棧不靈活,但是很嚴(yán)格,是安全的,易于管理。因?yàn)橹灰厦娴囊脹]有銷毀,下面引用就一定還在,在大部分程序中,都是先定義的變量、引用先進(jìn)棧,后定義的后進(jìn)棧,同時(shí),區(qū)塊內(nèi)部的變量、引用在進(jìn)入?yún)^(qū)塊時(shí)壓棧,區(qū)塊結(jié)束時(shí)出棧,理解了這種機(jī)制,我們就可以很方便地理解各種編程語言的作用域的概念了,同時(shí)這也是棧的優(yōu)點(diǎn)——錯誤的引用邏輯在編譯時(shí)就可以被發(fā)現(xiàn)。

棧--主要存放引用和基本數(shù)據(jù)類型。

堆--用來存放 new 出來的對象實(shí)例。


內(nèi)存溢出和內(nèi)存泄漏

內(nèi)存溢出 out of memory,是指程序在申請內(nèi)存時(shí),沒有足夠的內(nèi)存空間供其使用,出現(xiàn)out of memory;比如申請了一個integer,但給它存了long才能存下的數(shù),那就是內(nèi)存溢出。

內(nèi)存泄露 memory leak,是指程序在申請內(nèi)存后,無法釋放已申請的內(nèi)存空間,一次內(nèi)存泄露危害可以忽略,但內(nèi)存泄露堆積后果很嚴(yán)重,無論多少內(nèi)存,遲早會被占光。

memory leak會最終會導(dǎo)致out ofmemory。

 

Java 堆內(nèi)存的OutOfMemoryError異常是實(shí)際應(yīng)用中最常見的內(nèi)存溢出異常情況。出現(xiàn)Java 堆內(nèi)存溢出時(shí),異常堆棧信息“java.lang.OutOfMemoryError”會跟著進(jìn)一步提示“Java heapspace”。

要解決這個區(qū)域的異常,一般的手段是首先通過內(nèi)存映像分析工具(如Eclipse Memory Analyzer)對dump 出來的堆轉(zhuǎn)儲快照進(jìn)行分析,重點(diǎn)是確認(rèn)內(nèi)存中的對象是否是必要的,也就是要先分清楚到底是出現(xiàn)了內(nèi)存泄漏(Memory Leak)還是內(nèi)存溢出(Memory Overflow)。

如果是內(nèi)存泄漏,可進(jìn)一步通過工具查看泄漏對象到GC Roots 的引用鏈。于是就能找到泄漏對象是通過怎樣的路徑與GC Roots 相關(guān)聯(lián)并導(dǎo)致垃圾收集器無法自動回收它們的。掌握了泄漏對象的類型信息,以及GC Roots 引用鏈的信息,就可以比較準(zhǔn)確地定位出泄漏代碼的位置。

如果不存在泄漏,換句話說就是內(nèi)存中的對象確實(shí)都還必須存活著,那就應(yīng)當(dāng)檢查虛擬機(jī)的堆參數(shù)(-Xmx 與-Xms),與機(jī)器物理內(nèi)存對比看是否還可以調(diào)大,從代碼上檢查是否存在某些對象生命周期過長、持有狀態(tài)時(shí)間過長的情況,嘗試減少程序運(yùn)行期的內(nèi)存消耗。


內(nèi)存分配過程

1、JVM 會試圖為相關(guān)Java對象在Eden Space中初始化一塊內(nèi)存區(qū)域。

2、當(dāng)Eden空間足夠時(shí),內(nèi)存申請結(jié)束;否則到下一步。

3、JVM 試圖釋放在Eden中所有不活躍的對象(這屬于1或更高級的垃圾回收)。釋放后若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區(qū)。

4、Survivor區(qū)被用來作為Eden及Old的中間交換區(qū)域,當(dāng)Old區(qū)空間足夠時(shí),Survivor區(qū)的對象會被移到Old區(qū),否則會被保留在Survivor區(qū)。

5、當(dāng)Old區(qū)空間不夠時(shí),JVM 會在Old區(qū)進(jìn)行完全的垃圾收集(0級)。

6、完全垃圾收集后,若Survivor及Old區(qū)仍然無法存放從Eden復(fù)制過來的部分對象,導(dǎo)致JVM無法在Eden區(qū)為新對象創(chuàng)建內(nèi)存區(qū)域,則出現(xiàn)“outofmemory”錯誤。


對象訪問

對象訪問在Java 語言中無處不在,是最普通的程序行為,但即使是最簡單的訪問,也會卻涉及Java 棧、Java 堆、方法區(qū)這三個最重要內(nèi)存區(qū)域之間的關(guān)聯(lián)關(guān)系,如下面的這句代碼:

 

Object obj = newObject();

 

假設(shè)這句代碼出現(xiàn)在方法體中,那“Object obj”這部分的語義將會反映到Java 棧的本地變量表中,作為一個reference 類型數(shù)據(jù)出現(xiàn)。而“new Object()”這部分的語義將會反映到Java 堆中,形成一塊存儲了Object 類型所有實(shí)例數(shù)據(jù)值(Instance Data,對象中各個實(shí)例字段的數(shù)據(jù))的結(jié)構(gòu)化內(nèi)存,根據(jù)具體類型以及虛擬機(jī)實(shí)現(xiàn)的對象內(nèi)存布局(Object Memory Layout)的不同,這塊內(nèi)存的長度是不固定的。另外,在Java 堆中還必須包含能查找到此對象類型數(shù)據(jù)(如對象類型、父類、實(shí)現(xiàn)的接口、方法等)的地址信息,這些類型數(shù)據(jù)則存儲在方法區(qū)中。

由于reference 類型在Java 虛擬機(jī)規(guī)范里面只規(guī)定了一個指向?qū)ο蟮囊?,并沒有定義這個引用應(yīng)該通過哪種方式去定位,以及訪問到Java 堆中的對象的具體位置,因此不同虛擬機(jī)實(shí)現(xiàn)的對象訪問方式會有所不同,主流的訪問方式有兩種:使用句柄直接指針。

如果使用句柄訪問方式,Java 堆中將會劃分出一塊內(nèi)存來作為句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實(shí)例數(shù)據(jù)和類型數(shù)據(jù)各自的具體地址信息。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    欧美丰满人妻少妇精品| 亚洲高清中文字幕一区二区三区| 日本午夜一本久久久综合| 午夜精品一区二区av| 久久这里只有精品中文字幕| 亚洲熟妇中文字幕五十路| 91人妻丝袜一区二区三区| 深夜视频成人在线观看| 亚洲中文字幕一区三区| 久久99青青精品免费观看| 欧美日韩国产综合在线| 色丁香一区二区黑人巨大| 亚洲一区二区精品久久av| 99热在线播放免费观看| 日本人妻免费一区二区三区| 亚洲内射人妻一区二区| 欧美国产日产综合精品| 99热中文字幕在线精品| 国产欧美日韩一级小黄片| 99日韩在线视频精品免费| 国产精品99一区二区三区| 国产麻豆一线二线三线| 色一欲一性一乱—区二区三区 | 在线九月婷婷丁香伊人| 久久亚洲精品成人国产| 少妇人妻一级片一区二区三区| 国产精品一区二区三区日韩av| 一区二区欧美另类稀缺| 免费在线成人激情视频| 偷自拍亚洲欧美一区二页| 亚洲妇女黄色三级视频| 日韩成人中文字幕在线一区| 风韵人妻丰满熟妇老熟女av| 久久一区内射污污内射亚洲| av免费视屏在线观看| 色婷婷成人精品综合一区| 正在播放玩弄漂亮少妇高潮| 日本加勒比系列在线播放| 欧美精品在线播放一区二区| 精品人妻久久一品二品三品| 91欧美日韩中在线视频|