「軟帝學院」深入理解Java的內存分配和管理

「軟帝學院」深入理解Java的內存分配和管理

熟悉java的內存分配機制,能夠根據實際情況選擇更合適的數據結構與變量類型,能夠有效地提高程序運行效率!

同時,在代碼出現bug的時候,懂一些內存知識,能夠快速找到錯誤的本質原因!

Java內存分配時涉及的區域:

寄存器:在程序中無法控制;

棧:存放基本類型的數據和對象的引用,但是對象本身不存放在棧中,而是存放在堆中;

堆:存放用new產生的數據;

靜態域:存放在對象中用static定義的靜態成員;

常量池:存放常量。

內存分配中的棧和堆

1.棧

在函數中定義的一些基本類型的變量數據,還有對象的引用變量都在函數的棧內存中分配。當在一段代碼中定義一個變量時,Java就在棧中為這個變量分配內存空間,當該變量退出該作用域後,java會自動釋放掉為該變量分配的內存空間。

棧內存,是java程序的運行區,是在線程創建時創建的。它的生命周期跟隨線程的生命周期,線程結束棧內存也就釋放,對於棧來說不存在垃圾回收問題,隻要線程結束,該棧就結束瞭。

棧中的數據是以棧幀(stackframe)的格式存在的。棧幀是一個內存區塊,是一個數據集,是有關方法(method)和運行期數據的數據集。當一個方法A被調用時就會產生一個棧幀F1被壓入到棧中,A方法再調用B方法,就會產生棧幀F2被壓入棧,執行完畢後,先彈出F2,在彈出F1。

2.堆

堆內存用來存放由關鍵字new創建的對象和數組。在堆中分配的內存,由java虛擬機自動垃圾收集器來管理。

在堆中創建一個對象後,還可以在棧中定義一個變量,讓這個變量的值等於對象在堆內存中的首地址,棧中的變量就是對象的引用,相當於java中的指針。當程序運行到對象所在的語句塊之外,對象占據的內存不會自動釋放,在沒有引用變量指向它時,隨後一個不確定的時間被垃圾收集器回收掉。

3.常量池(constantpool)

常量池指的是在編譯期被確定,並被保存在已編譯的.class文件中的一些數據。除瞭包含代碼中所定義的各種基本類型(int,long等)和對象型(string、數組等)的常量值(final),還包含一些以文本形式出現的符號引用。

類和接口的全限定名;

字段的名稱和描述符;

方法和名稱的描述符;

在程序執行時,常量池會存儲在MethodArea(方法區)中,而不是堆中。

一個java虛擬機實例隻存在一個堆內存,堆內存的大小是可以調節的,類加載器讀取瞭類文件後,需要把類、方法、常變量(const修飾的變量)放到堆內存中,堆內存分為三部分:

1)PermanentSpace永久存儲區

永久存儲區是一個常駐內存區域,用於存放jdk自身所攜帶的ClassInterface的元數據。也就是說它存儲的是運行環境必需的類信息,被裝載到此區域的數據不會被垃圾收集器回收,關閉java虛擬機才會釋放此區域占的內存。

2)YoungGeneration Space新生區

新生區是類的誕生、成長、消亡的區域,新生區又分兩部分:伊甸區(Edenspace)和幸存區(Survivorspace),所有的類都是在伊甸區被new出來的。幸存區有兩個:0區(survivor0 space)和1區(survivor1space)。當伊甸區的空間用完時,程序再創建對象,虛擬機將對伊甸區進行垃圾回收,將伊甸區中的不再被引用的對象進行銷毀,然後將伊甸區中的剩餘對象移動到幸存0區,如果幸存0區也滿瞭,將對該區進行垃圾回收,然後移動到1區,如果1區也滿瞭,就會移動到老年區。

3)Tenuregeneration space老年區

老年區保存從新生區帥選出來的java對象。

Java虛擬機中為什麼分堆區,棧區?

1)從軟件的角度,棧區代表瞭處理邏輯,而堆代表瞭數據。分開,使得處理邏輯更為清晰,體現瞭模塊化的思想。

2)虛擬機堆、棧的分離,使得堆中的內容可以被多個虛擬機棧共享(也可以理解為多個線程訪問同一個對象,因為虛擬機棧是隨著線程的創建而創建的),這種共享的益處很多,一方面提供瞭一種有效的數據交互方式(如共享內存);另一個方面,堆中的共享常量和緩存可被多有虛擬機棧訪問,節省瞭空間。

4,堆和棧的合作

堆是一個運行時數據區,類的對象從中分配空間,堆的優勢是可以動態地分配內存大小,生存期不必事先告訴編譯器,但是缺點是,由於在運行時動態分配內存,存取速度慢。

棧的優勢是存取速度比堆快,僅次於寄存器,缺點是棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中存放一些基本類型的變量數據和對象引用。

棧有一個重要特性,就是棧中的數據可以共享。

Int a =3;

int b =3;

編譯器先處理inta =3;首先會在棧中創建一個變量為a的引用,然後查找棧中是否有3這個值,如果沒有,就將3存放進來,然後將a指向3。接著處理intb =3;在創建完b的引用變量後,因為在棧中已經有3這個值,便將b直接指向3。這樣,a與b均指向3。如果再有語句a=4;編譯器會重新搜索棧中是否有4值,如果沒有,將4存放進來,並將a指向4,如果已經有瞭,直接將a指向這個地址。因此a的改變不會影響到b的值。

這種數據的共享與兩個對象的引用同時指向一個對象的共享是不同的,因為這種情況下a的修改不會影響到b,它是由編譯器完成的,有利於節省空間。而一個對象的引用變量修改瞭這個對象的內部狀態,會影響到另一個對象引用變量。

Java中,String是一個特殊的包裝類數據,有兩種創建方式:

String str = newString(“abc”);

String str = “abc”;

第一種用new創建對象,是存在堆中,沒調用一次會創建一個新的對象。

第二種是先在棧中創建一個對String類的對象引用變量str,然後通過符號引用去字符串常量池裡查找有沒有“abc”,如果沒有,將“abc”放進字符串常量池,並讓str指向“abc”,如果有,直接讓str指向“abc”。

5, 運行時的數據區域

所有線程共享方法區和堆;

虛擬機棧、本地方法棧和程序計數器是線程隔離的數據區。

1) 程序計數器(ProgramCounter Register)

程序計數器是一塊較小的內存空間,作用相當於當前線程所執行的字節碼的行號指示器。在java虛擬機概念裡,字節碼解釋器通過改變這個計數器的值來選取下一條需要執行的字節碼指令,很多基礎功能都需要依賴這個計數器來完成,如分支,循環,跳轉等。

每條線程都需要一個獨立的程序計數器,並且各條線程之間的計數器互不影響,能夠獨立存儲。

2) Java的虛擬機棧VMStack

虛擬機棧是類中方法的執行過程的內存模型,與程序計數器一樣,虛擬機棧也是線程私有的,它的聲明周期與線程相同。虛擬機棧描述的是java方法執行的內存模型:每個方法被執行的時候都會同時創建一個棧幀(stackframe)用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每個方法被調用直至執行完成的過程,就對應一個棧幀在虛擬機中從入棧到出棧的過程。

棧幀是虛擬機棧的棧元素。對於活動線程中棧頂的棧幀,成為當前棧幀,這個棧幀所關聯的方法稱為當前方法,正在執行的字節碼指令都隻針對當前棧幀進行操作。

通常把java內存分為堆內存(heap)和棧內存(stack),其中的棧就是虛擬機棧,或者說是虛擬機棧中的局部變量表部分。

局部變量表存放瞭編譯期可知的各種基本數據類型、對象引用、返回地址類型。

3) Java堆

堆是類實例和數組的分配空間,是多有線程共享的內存區域。堆可以處於物理上不連續的內存空間中,隻要邏輯上是連續的即可。

4) 方法區

方法區是虛擬機啟動時創建,是多有線程共享的內存區域。用來存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。

這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載。

5) 運行時常量池

運行時常量池(RuntimeConstantPool)是方法區的一部分。用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載後存放到方法區的運行時常量池中。

(最後,大傢喜歡我的文章話,可以關註我,有時間記得幫我點下轉發讓更多的人看到哦。)

Published in News by Awesome.

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *