java虛擬機(jī)中內(nèi)存管理機(jī)制的示例分析

這篇文章給大家分享的是有關(guān)java虛擬機(jī)中內(nèi)存管理機(jī)制的示例分析的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過來看看吧。

創(chuàng)新互聯(lián)公司提供網(wǎng)站制作、成都做網(wǎng)站、網(wǎng)頁設(shè)計(jì),品牌網(wǎng)站制作,1元廣告等致力于企業(yè)網(wǎng)站建設(shè)與公司網(wǎng)站制作,十載的網(wǎng)站開發(fā)和建站經(jīng)驗(yàn),助力企業(yè)信息化建設(shè),成功案例突破1000+,是您實(shí)現(xiàn)網(wǎng)站建設(shè)的好選擇.

先讓我們來看張圖

java虛擬機(jī)中內(nèi)存管理機(jī)制的示例分析

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

Java虛擬機(jī)在執(zhí)行Java程序的過程中會(huì)把它所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域,這些區(qū)域都擁有自己的用途,并隨著JVM進(jìn)程的啟動(dòng)或者用戶線程的啟動(dòng)和結(jié)束建立和銷毀。

先讓我們了解下進(jìn)程和線程的區(qū)別:

進(jìn)程是資源分配的最小單位,線程是程序執(zhí)行的最小單位。

進(jìn)程有自己的獨(dú)立地址空間,每啟動(dòng)一個(gè)進(jìn)程,系統(tǒng)就會(huì)為它分配一個(gè)地址空間、建立數(shù)據(jù)表來維護(hù)代碼段、堆棧段和數(shù)據(jù)段,這種操作非常昂貴。而線程是共享進(jìn)程中的數(shù)據(jù)的,使用相同的地址空間,因此CPU切換一個(gè)線程的花費(fèi)要比進(jìn)程小很多,同時(shí)創(chuàng)建一個(gè)線程的開銷也要比進(jìn)程小很多。

同一個(gè)進(jìn)程中可以包括多個(gè)線程,并且線程共享整個(gè)進(jìn)程的資源(寄存器、堆棧、上下文),一個(gè)進(jìn)程至少包含一個(gè)線程。線程之間的通信更加方便,同一進(jìn)程下的線程共享全局變量、靜態(tài)變量等數(shù)據(jù),而進(jìn)程之間的通信則需要以通信的方式(IPC)進(jìn)行。

這里引用在知乎中某位同學(xué)的解釋,

進(jìn)程的顆粒度太大,每次都要有上下的調(diào)入,保存,調(diào)出。如果我們把進(jìn)程比喻為一個(gè)運(yùn)行在電腦上的軟件,那么一個(gè)軟件的執(zhí)行不可能是一條邏輯執(zhí)行的,必定有多個(gè)分支和多個(gè)程序段,就好比要實(shí)現(xiàn)程序A,實(shí)際分成 a,b,c等多個(gè)塊組合而成。那么這里具體的執(zhí)行就可能變成:

程序A得到CPU =》CPU加載上下文,開始執(zhí)行程序A的a小段,然后執(zhí)行A的b小段,然后再執(zhí)行A的c小段,最后CPU保存A的上下文。

這里a,b,c的執(zhí)行是共享了A的上下文,CPU在執(zhí)行的時(shí)候沒有進(jìn)行上下文切換的。

看到這里是不是對(duì)線程共享和線程隔離區(qū)有了一個(gè)更深次的理解??梢岳斫鉃榉椒▍^(qū)和堆是分配給進(jìn)程的,也就是線程共享區(qū),而棧和程序計(jì)數(shù)器則是分配給每個(gè)獨(dú)立線程的。

在SUN公司的HotSpot虛擬機(jī)中將java虛擬機(jī)棧和本地方法棧合二為一了

程序計(jì)數(shù)器(Program Counter Register)

程序計(jì)數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,它可以看成是當(dāng)前線程所執(zhí)行字節(jié)碼的行號(hào)指示器。在計(jì)算機(jī)中,其實(shí)程序計(jì)數(shù)器就是一個(gè)寄存器,依據(jù)不同計(jì)算機(jī)細(xì)節(jié)的差異,它可以存放當(dāng)前正在被執(zhí)行的指令,也可以放下一個(gè)被執(zhí)行的指令。

在虛擬機(jī)的概念模型中,字節(jié)碼解釋器工作時(shí)就是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令的。

由于Java虛擬機(jī)的多線程是通過線程輪詢切換并分配處理器執(zhí)行時(shí)間的方式來實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)候,一個(gè)處理器都只會(huì)執(zhí)行一條線程中的指令,因此為了線程切換之后能過恢復(fù)到正確的執(zhí)行位置,每條線程都需要擁有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各條線程之間的計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ),所以程序計(jì)數(shù)器是線程私有的內(nèi)存,也就是它屬于線程隔離區(qū)的。

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

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

Java虛擬機(jī)棧

Java虛擬機(jī)棧(Java Virtual Machine Stack)也是線程私有的,即他的生命周期和線程相同。

在Java中,JVM中的棧記錄了線程的方法調(diào)用,每個(gè)線程擁有一個(gè)棧,在某個(gè)線程的運(yùn)行過程中,如果有新的方法調(diào)用,那么該線程對(duì)應(yīng)的棧就會(huì)增加一個(gè)存儲(chǔ)單元,即棧針(Stack Frame)。

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

當(dāng)被調(diào)用方法運(yùn)行結(jié)束時(shí),該方法對(duì)應(yīng)的幀將被刪除,參數(shù)和局部變量所占據(jù)的空間也隨之釋放。線程回到原方法,繼續(xù)執(zhí)行。當(dāng)所有的棧都清空時(shí),程序也隨之運(yùn)行結(jié)束。

我們經(jīng)常說的棧內(nèi)存其實(shí)就是現(xiàn)在講的虛擬機(jī)棧,或者說是虛擬機(jī)棧中局部變量表部分。

局部變量表存放了編譯器可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對(duì)象引用(reference類型,它不等同于對(duì)象本身,可能是指向?qū)ο笃鹗嘉恢玫囊弥羔?,也可能是指向一個(gè)代表對(duì)象的句柄或其他與此對(duì)象相關(guān)的位置,引用所指向的對(duì)象保存在堆中(引用可能為Null,即不指向任何對(duì)象))和returnAddress類型(指向了一條字節(jié)碼指令的地址)。

其中64位長(zhǎng)度的long和double類型的數(shù)據(jù)會(huì)占用2個(gè)局部變量空間(Slot),其余數(shù)據(jù)類型只占用1個(gè)。局部變量表所需要的內(nèi)存空間在編譯時(shí)期完成分配。當(dāng)進(jìn)入一個(gè)方法時(shí),這個(gè)方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運(yùn)行期間不會(huì)改變局部變量表的大小。

異常有兩種

1,線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度將拋出StackOverflowError異常 (遞歸調(diào)用)

2,如果虛擬機(jī)可以動(dòng)態(tài)擴(kuò)展,如果擴(kuò)展時(shí)已經(jīng)無法申請(qǐng)到足夠的內(nèi)存就會(huì)拋出OutOfMemeoryError異常。

List list=new ArrayList();
  for(;;){
   int[] tmp=new int[1000000];
   list.add(tmp);
  }

本地方法棧

本地方法棧(Native Method Stack)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的。他們之間的區(qū)別就是Java虛擬機(jī)棧是位虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧為位虛擬機(jī)使用到的Native方法服務(wù)。

其實(shí)虛擬機(jī)規(guī)范中對(duì)本地方發(fā)棧中方法所使用的語言、使用方式以及數(shù)據(jù)結(jié)構(gòu)都沒有強(qiáng)制規(guī)定,因此具體的虛擬機(jī)可以自由地實(shí)現(xiàn)它。甚至在有的虛擬機(jī)(如Sun HotSpot虛擬機(jī))直接就把本地方法棧和虛擬機(jī)棧合二為一。與虛擬機(jī)棧一樣,本地方法棧區(qū)域也會(huì)拋出StackOverflowError和OutOfMemory異常。

Java堆

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

堆中可細(xì)分為新生代和老年代,在細(xì)分可以分為Eden空間、Form Survivor空間、to Survivor空間。

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

根據(jù)Java虛擬機(jī)規(guī)范規(guī)定,Java堆可以處于物理上不連續(xù)的內(nèi)存中,即只要邏輯上是連續(xù)的即可,就像我們的磁盤空間一樣。在實(shí)現(xiàn)時(shí),可以固定大小也是可擴(kuò)展的。主流的虛擬機(jī)都是按照可擴(kuò)展來實(shí)現(xiàn)的(通過-Xmx和-Xms來控制)。如果在堆中沒有內(nèi)存可分配,并且堆也無法繼續(xù)擴(kuò)展時(shí),將會(huì)拋出OutOfMemortError異常。

Java的普通對(duì)象存活在堆中,與棧不同,堆的空間不會(huì)隨著方法調(diào)用結(jié)束而清空。因此,在某個(gè)方法中創(chuàng)建的對(duì)象,可以在方法調(diào)用結(jié)束之后,繼續(xù)存在堆中。這帶來的一個(gè)問題是,如果我們不斷的創(chuàng)建新的對(duì)象,內(nèi)存控件將會(huì)最終消耗殆盡。

方法區(qū)

方法區(qū)(Method Area)與Java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已經(jīng)被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯之后的代碼等數(shù)據(jù)。雖然Java虛擬機(jī)將其描述為堆的一個(gè)邏輯部分,但它卻有一個(gè)別名叫做Non-Heap(非堆)。目的是與Java堆區(qū)分開來。(以前很多人把方法區(qū)稱為永久代,現(xiàn)在JDK1.8中已經(jīng)用元數(shù)據(jù)區(qū)域取代了永久代)。

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

運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息就是常量池。用于存放編譯期生成的各種字面量和符號(hào)引用,這部分內(nèi)容將在類加載后進(jìn)入到方法區(qū)的運(yùn)行時(shí)常量池中存放。并非預(yù)置入Class文件中常量池的內(nèi)容才進(jìn)入方法運(yùn)行時(shí)常量池,運(yùn)行期間也可能將新的常量放入池中,這種特性被開發(fā)人員利用得比較多的便是String類的intern()方法。

public class Test
{
 public static void main(String[] args)
 {
  String s1="hello china";//字節(jié)碼常量
  String s2="hello china";
  String s3=new String("hello china");

  System.out.println(s1 == s2);
  System.out.println(s1 == s3);
  System.out.println(s1 == s3.intern());//運(yùn)行時(shí)常量 intern 是個(gè)native方法
 }
}

java虛擬機(jī)中內(nèi)存管理機(jī)制的示例分析

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

注:JDK8之前,方法區(qū)由永久代實(shí)現(xiàn),主要存放類的信息、常量池、方法數(shù)據(jù)、方法代碼等;JDK8之后,取消了永久代,提出了元空間,并且常量池、靜態(tài)成員變量等遷移到了堆中;元空間不在虛擬機(jī)內(nèi)存中,而是放在本地內(nèi)存中。

直接內(nèi)存

由于直接內(nèi)存(Direct Memory)并不是java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域,但是這部分也被頻繁使用,而且也可能導(dǎo)致內(nèi)存溢出異常出現(xiàn),所以也放到這一部分進(jìn)行簡(jiǎn)介。

先讓,本機(jī)直接內(nèi)存的分配不會(huì)受到Java堆大小的限制,但是肯定還是會(huì)受到本機(jī)總內(nèi)存大小以及處理器尋址空間的限制。管理員在配置虛擬機(jī)參數(shù)時(shí),會(huì)根據(jù)實(shí)際內(nèi)存設(shè)置-Xmx等參數(shù)信息,但經(jīng)常會(huì)忽略直接內(nèi)存,使得各個(gè)內(nèi)存區(qū)域總和大于物理內(nèi)存限制(包含物理的和操作系統(tǒng)級(jí)的限制),從而導(dǎo)致動(dòng)態(tài)擴(kuò)展時(shí)出現(xiàn)OutOfMemoryError異常。

JDK1.4加入了NIO,引入一種基于通道與緩沖區(qū)的I/O方式,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個(gè)存儲(chǔ)在Java堆中的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。因此避免了在Java堆和Native堆中來回復(fù)制數(shù)據(jù),提高了性能。 

Object obj = new Object();

Object obj將會(huì)反映到虛擬機(jī)棧中(reference類型)

new Object()將會(huì)反映到Java堆中

此類的對(duì)象類型、父類、實(shí)現(xiàn)的接口、方法等信息數(shù)據(jù),將反映到方法區(qū)中

感謝各位的閱讀!關(guān)于“java虛擬機(jī)中內(nèi)存管理機(jī)制的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

分享名稱:java虛擬機(jī)中內(nèi)存管理機(jī)制的示例分析
鏈接地址:http://bm7419.com/article48/jcsghp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App開發(fā)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)公司用戶體驗(yàn)、域名注冊(cè)、外貿(mào)建站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站建設(shè)