java虛擬機在執行java程序的時候會把它所管理的內存分爲多個不一樣的區域,每一個區域都有不一樣的做用,以及由各自的生命週期,有些隨着虛擬機進行的啓動而存在,有些區域則依賴於用戶線程的啓動或結束而創建或銷燬等。在《java虛擬機規範(Java SE7版)》中規定,java內存分爲如下一種,如圖所示:java
程序計數器(Program Counter Register)是一個內存較小的區域,它能夠被看做是當前線程所執行到的字節碼的行號的指示器,字節碼解釋器在執行下一條指令,好比分支,跳轉,循環,異常處理等都須要依賴這個計數器來完成的。程序員
因爲java多線程是經過不斷的切換分配處理器時間片的方式來實現的,在任何一個肯定的時刻,一個處理器都只會執行一條指令。也就是說,在多線程中每一個線程均可以搶到cpu的時間片,那麼別搶去的線程會當即中止下來,直到它再一次得到cpu的時間片。那麼,java虛擬機是如何確保再一次得到處理器時間片的時候可以在正確的位置上繼續執行指令?java虛擬機就是經過當前線程的程序計數器保證的,由於保存了當前線程上次執行結束的位置,所以,程序計數器是每一個線程獨有的,各線程以前的程序計數器互不影響,獨立存儲,咱們稱這類內存區域爲「線程私有」的內存。算法
若是線程正在執行java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址,若是正在執行的是native方法,那麼這個計數器值則爲空(Undefined);數據庫
由於程序計數器記錄的是當前線程字節碼執行指令的地址,因此它的內存大小是不會隨着線程的執行而發生變化,所以,該內存區域是惟一一個在java虛擬機規範中沒有規定任何OutOfMemeryError(內存溢出)的狀況的區域。數組
Java虛擬機棧與程序計數器同樣都是線程私有的,它的生命週期與線程一致,每當建立一個新的線程時都會產生一個新的的虛擬機棧,線程銷燬時虛擬機棧也隨即銷燬。每一個方法的執行都會建立一個棧幀用於存儲局部變量、操做數棧、動態鏈表,方法的出口等信息。每一個方法的執行到結束就對應着一個棧幀在虛擬機中入棧和出棧的過程。數據結構
咱們在平常開發的過程的中常常會把java內存分爲堆內存和棧內存,這個說法是不許確的,由於java內存區域的劃分遠比這個複雜的多,這種劃分的方法只能說明程序員最關注的,與對象內存分配關係最密切的內存區域這兩塊,這裏所說的棧其實指的是虛擬機中的局部變量表。局部變量表中存放了編譯期可知的基本數據類型(char,byte,int,boolean,short,float,long,double)、對象的引用(reference類型,指的是對象地址的引用指針或是句柄)、returnAddress類型(指向字節碼指令的地址)。多線程
局部變量表所需的內存空間在編譯期完成分配,當進入一個方法時,這個方法所須要的棧幀的大小就已經肯定了,在運行期間不會改變局部變量表的大小。其中64位長度的long和double類型的數據會佔用兩個局部變量空間(Slot),其他的數據類型只能佔用1個。線程
在java虛擬機規範中,對虛擬機棧定義了兩種異常:第一,若是線程請求的深度大於虛擬機棧所容許的最大深度,將拋出StackoverflowError異常,第二,若是虛擬機棧能夠動態的擴展,當擴展時沒法申請到足夠的內存,則會拋出OutOfMemoryError異常。指針
2.一、運行時棧幀視頻
棧幀是Java虛擬機用來進行方法的調用和方法的指定的數據結構,它是虛擬機運行時數據庫的Java虛擬機棧的棧元素。每個棧幀都包含局部變量表、操做數棧、動態連接、方法返回地址和一些額外的附加信息。
局部變量:方法的參數和方法中聲明的局部變量都存儲在局部變量中。在Class編譯的時候就已經肯定了的局部變量表的最大容量,聲明在方法的Code屬性的max_locals數據項中。變量槽(Slot)是局部變量表的最小單位,能夠存儲32位和64位的數據,64位的數據則須要兩個連續的變量槽來表示。
操做數棧:也成爲操做棧,是一個後進先出的結構。方法執行過程當中的算術運算或者調用其它方法的參數傳遞的時候都是在操做數棧中進行的。
動態連接:Class 文件中存放了大量的符號引用,字節碼中的方法調用指令就是以常量池中指向方法的符號引用做爲參數。這些符號引用一部分會類第一次加載或第一次引用的時候轉化爲直接引用,這種轉化稱爲靜態解析。另外一部分將在每一次運行期轉化爲直接引用,這部分稱爲動態鏈接。
返回地址:當一個方法執行完成後的出口,有兩個狀況:一是正常狀況退出,會將返回值傳遞給上一個方法的調用者,另外一種是異常狀況,此時是沒有返回值的。
本地方法棧和java虛擬機棧的做用是差很少的,他們的惟一區別在於:java虛擬機棧爲虛擬機執行java方法(字節碼)服務,而本地方法棧則爲虛擬機中Native方法服務。在虛擬機規範中並無明確的規定本地方法棧使用何種語言與數據結構,可有具體的虛擬機去實現它。本地方法棧和java虛擬機棧同樣,也會拋出StackoverflowError和OutOfMemoryError兩個異常。
java堆是java內存管理區域中最大的一塊,java堆是全部線程共享的一塊內存區域,在虛擬機啓動時建立。java堆是用來存放對象,幾乎全部的java對象和數組都存放在java隊中,都在堆中分配空間。
java堆是當即回收器管理的主要區域,所以有人也會把java堆稱爲「GC堆」。從垃圾回收的角度來看,因爲如今大多數的收集器都是採用分代收集算法,因此java堆還能夠細分爲:新生代和老年代;再細緻一點有能夠分爲Eden空間,From Survivor空間、To Survivor空間等;從內存分配的角度來看,線程共享的java堆中可能劃分爲多個線程私有的分配緩衝區(TLAB)。這樣分配是爲了更好的回收內存和建立內存等,與存放的區域無關,都是存放java對象。
根據java虛擬機規範的規定,java堆能夠是物理上不連續的內存空間,只要邏輯上連續就能夠了。在實現時能夠設置固定大小的空,也能夠動態擴展的,不過當前流程的虛擬機都是能夠動態來擴展的。若是在堆中沒有足夠的空間來分配實例對象,或者沒法擴展堆空間,那麼就會拋出OutOfMemoryError異常。
方法區和java堆同樣,都是線程共享的內存區域,它主要用於存儲已被虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯後的代碼等數據。雖然java虛擬機規範把方法區描述爲java堆的一部分,可是它卻有一個別名叫作Non-Heap(非堆),目的是與java堆分開。
java虛擬機規範對方法區的限制很是的寬鬆,除了和java堆同樣不須要物理上連續的內存和能夠選擇固定大小空間或動態擴展爲,還能夠選擇不實現垃圾回收。相對而言,垃圾回收是比較少在這個區域中出現的,但並不是進入到方法區的數據都能永久的存在的。這塊區域的的內存回收目標主要是針對常量池的回收和對類型的卸載,可是回收的效果並非很是明顯,特別是對類型的卸載,條件很是的苛刻,但這塊內存的回收是很是必要的。在SUN公司的BUG列表中,曾經出現過若干個很是嚴重的BUG,就是因爲低版本中Hotspot虛擬機沒有對此內存區域進行回收形成的。
根據java虛擬機規範的規定,當方法區沒法知足內存分配的需求時,將拋出OutOfMemoryError異常。
運行時常量池是屬於方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯期生成的各類字面量和符號引用,這部分的內容將在類加載完成 後進入到方法區的運行時常量池中存放。
運行時常量池相對於Class文件常量池的另外一個特徵是具有動態性,java語言並不要求常量必定只有編譯期才能產生,也就是並不是預置入Class文件中的常量池的內容才能夠進入方法區的運行時常量池,運行期間也能夠將新的常量放入到池中,好比String類的intern方法。
既然運行時常量池是存在於方法區中的,那麼在沒法分配到足夠的內存是也會拋出OutOfMemoryError異常。
參考書籍: 《深刻理解Java虛擬機》
歡迎你們關注公衆號: 【java解憂雜貨鋪】,裏面會不定時發佈一些技術博客;關注便可免費領取大量最新,最流行的技術教學視頻: