day11-java內存區域


參考書籍:[(深入理解JAVA虛擬機)]

概述

在內存管理領域,對C和C++程序開發人員來說,他們擁有每個對象的所有權,但是也擔負着每個對象生命從開始到銷燬的維護責任。對於java程序員來說,在jvm自動內存管理機制的幫助下,不在需要爲每個new操作去管理,不容易出現內存泄漏和內存溢出的問題,但是正因爲如此一旦出現了內存泄漏和內存溢出,如果不瞭解jvm是怎樣管理使用內存的話,那麼排查錯誤將是一個十分艱難的工作。
Java跨平臺原因:在下載jdk時,我們選擇了對應操作系統的jdk版本,不同的jdk對jvm有不同的實現,從軟件層面屏蔽不同操作系統在底層硬件與指令上的區別

我們寫好一個類後,先編譯爲字節碼文件,把字節碼文件扔進jvm中去運行,先由類裝載子系統,把字節碼文件裝載到運行時數據區,再由字節碼執行引擎執行內存代碼

在這裏插入圖片描述

StackOverflowError&OutOfMemoryError

StackOverflowError代表的是,當棧深度超過虛擬機分配給線程的棧大小時就會出現此error
OutofMemoryError代表的是,當再申請新的內存時,虛擬機分配給線程的內存大小中無法再分配新的內存,就會出現此error

運行時數據區

Java虛擬機在執行Java程序的過程中,會把他所管理的內存劃分爲若干個不同的數據區域,這些區域有各自的用途、以及創建和銷燬的時間,有的區域隨着虛擬機進程的啓動而一直存在,有些區域則是依賴用戶線程的啓動和結束而創建和銷燬。

在這裏插入圖片描述
線程私有的:程序計數器,虛擬機棧,本地方法棧
線程共享的:堆,方法區,直接內存

程序計數器

程序計數器是一塊較小的內存空間,他可以看作是當前線程所執行的字節碼的行號指示器,在java虛擬機的概念模型裏,字節碼實際工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,他是程序控制流的指示器分支、循環、跳轉、異常處理、線程恢復等基礎功能,都需要依賴這個計數器來完成。
由於Java虛擬機的多線程是通過線程輪流轉換、分配處理器執行期間的方式來實現的,在任何一個確定的時刻,一個處理器(對於多核處理器來說是一個內核)都只會執行一條線程中的指令。因此,爲了線程切換後能恢復到正確的執行位置每條線程都需要有一個獨立的程序計數器
各條線程之間計數器互不影響獨立存儲,我們稱這類內存區域爲線程私有的內存
注意,如果線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址,如果正在執行的是本地方法,這個計數器值則爲空。此內存區域是唯一一個在Java虛擬機規範中沒有規定OutOfMemoryError情況的區域。它的生命週期隨着線程的創建而創建,隨着線程的結束而死亡。

java虛擬機棧

java虛擬機棧是線程私有的,它的生命週期與線程相同,虛擬機棧描述的是Java方法執行的線程內存模型:每個方法被執行的時候,java虛擬機都會同步創建一個棧幀。用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息,每個方法被調用直至執行完畢的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。
先調用的方法,先開闢棧幀內存空間,後調用的方法,後開闢棧幀空間,同時也先執行完,先銷燬棧幀空間,而先調用的方法最後執行完畢,才釋放棧幀空間,符合大學是學的棧的思想:先進後出原則。
StackOverflowError代表的是,當棧深度超過虛擬機分配給線程的棧大小時就會出現此error,此時如果類似遞歸等操作就可能會出現這個錯誤。

局部變量表:存放局部變量,局部變量表存放了編譯期可知的各種Java虛擬機基本數據類型、對象引用、返回值類型,這些數據類型在局部變量表中的存儲空間以局部變量槽slot來表示。其中64位長度的long和double類型的數據會佔用兩個變量槽,其餘的數據類型只佔用一個。局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。(這裏的大小指的是變量槽的數量,在虛擬機真正使用多大的內存空間來實現一個變量槽,還與具體的jvm實現有關。)
操作數棧:暫存程序運行過程中操作數的臨時中轉內存空間
動態鏈接:要執行方法在方法區的入口地址
方法出口:方法執行完後應該執行的位置。

public class Math {
	public static final int initData = 666; 
	public static User user = new User ();
	public int compute() { //-- 個方法對應一塊棧幀內存區域
		int a = 1 :
		int b = 2;
		int c = ( a + b ) * 10 ;
		return c ;
	}
	public static void main(String[] args){
		Math math = new Math() ;
		math.compute ();
		System.out.println( test" );
	}
}

在這裏插入圖片描述

本地方法棧

本地方法棧與虛擬機棧所發揮的作用是非常相似的,其區別是虛擬機棧爲虛擬機執行java方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的本地Native方法服務。

方法區

方法區與java堆一樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類型信息、常量、靜態變量、即時編譯器編譯後的代碼緩存等數據。

java堆

java堆是虛擬機所管理的內存中最大的一塊,Java堆是被所有線程共享的一塊內存區域,在虛擬機啓動時創建此內存區域的唯一目的就是存放對象實例Java世界裏 幾乎 所有的對象實例都在這裏分配內存。java堆是垃圾回收機制(垃圾收集器)所管理的內存區域,因此也稱gc堆。
從垃圾回收的角度,由於現在收集器基本都採用分代垃圾收集算法,所以Java堆還可以細分爲:新生代和老年代:其中新生代又分爲:Eden空間、From Survivor、To Survivor空間。進一步劃分的目的是更好地回收內存,或者更快地分配內存。“分代回收”是基於這樣一個事實:對象的生命週期不同,所以針對不同生命週期的對象可以採取不同的回收方式,以便提高回收效率。從內存分配的角度來看,線程共享的java堆中可能會劃分出多個線程私有的分配緩衝區(Thread Local Allocation Buffer,TLAB)。
在這裏插入圖片描述
如圖所示,JVM內存主要由新生代、老年代、永久代構成。

  • ① 新生代(Young Generation):大多數對象在新生代中被創建,其中很多對象的生命週期很短。每次新生代的垃圾回收(又稱Minor GC)後只有少量對象存活,所以選用複製算法,只需要少量的複製成本就可以完成回收。

新生代內又分三個區:一個Eden區,兩個Survivor區(一般而言),大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被複制到兩個Survivor區(中的一個)。當這個Survivor區滿時,此區的存活且不滿足“晉升”條件的對象將被複制到另外一個Survivor區。對象每經歷一次Minor GC,年齡加1,達到“晉升年齡閾值”後,被放到老年代,這個過程也稱爲“晉升”。顯然,“晉升年齡閾值”的大小直接影響着對象在新生代中的停留時間,在Serial和ParNew GC兩種回收器中,“晉升年齡閾值”通過參數MaxTenuringThreshold設定,默認值爲15。

  • ② 老年代(Old Generation):在新生代中經歷了N次垃圾回收後仍然存活的對象,就會被放到年老代,該區域中對象存活率高。老年代的垃圾回收(又稱Major GC)通常使用“標記-清理”或“標記-整理”算法。整堆包括新生代和老年代的垃圾回收稱爲Full GC(HotSpot VM裏,除了CMS之外,其它能收集老年代的GC都會同時收集整個GC堆,包括新生代)。
  • ③ 永久代(Perm Generation):主要存放元數據,例如Class、Method的元信息,與垃圾回收要回收的Java對象關係不大。相對於新生代和年老代來說,該區域的劃分對垃圾回收影響比較小。
在 JDK 1.8中移除整個永久代,取而代之的是一個叫元空間(Metaspace)的區域
(永久代使用的是JVM的堆內存空間,而元空間使用的是物理內存,直接受到本機的物理內存限制)。

運行時常量池

運行時常量池是方法區的一部分,class文件中除了有類的版本,字段,方法,接口等描述信息外,還有一項信息是常量池表:用於存放編譯器生成的各種字面量與符號引用,這部分內容將在類加載後存放到方法區的運行時常量池中。
在這裏插入圖片描述
運行時常量池相對於class文件常量池的另外一個重要特徵是具備動態性,Java語言並不要求常量一定有編譯期才能產生,也就是說,並非預置入class文件中常量池的內容才能進入方法去運行時常量池。運行期間也可以將新的常量放入池中,這種特性被開發人員利用的較多的地方便是string類的intern()方法中。

直接內存

直接內存並不是虛擬機運行數據區的一部分,但是這部分內存被頻繁使用,也可能導致OutOfMemoryError異常。
在JDK1.4中新加入了NOI(new input / out)類,引入了一種基於通道與緩衝區的I/O方式,他可以使用Native函數庫直接分配堆外內存,然後通過一個存儲在java堆裏面的DirectByteBuffer對象最爲這塊內存的引用進行操作,這樣能在一些場景中顯著提高性能,因爲避免了在java堆和Native堆中來回複製數據。
顯然,本機直接內存的分配不會受到Java堆大小的限制,但是既然是內存,則肯定還是會受到本機總內存(包括物理內存、SWAP分區或者分頁文件)大小以及處理器尋址空間的限制,一般服務器管理員配置虛擬機參數時,會根據實際內存去設置-Xmx參數信息,但是經常忽略掉直接內存,使得各個內存區域總和大於物理內存限制(包括物理和操作系統級的限制),從而導致動態擴展時出現OutOfMemoryError異常。
例如idea就配置了這個參數:
在這裏插入圖片描述


本文參考書籍:[(深入理解JAVA虛擬機)]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章