Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域。這些區域的用途各不相同,同時也依據着各自的執行規則,獨立的建立和銷燬數據。java
虛擬機內存的劃分,如圖所示:數組
線程之間互相獨立的區域有:數據結構
虛擬機棧 、本地方法棧、程序計數器多線程
線程能夠共享數據的區域:佈局
方法區 、堆性能
每一個區域的做用分別以下:spa
程序計數器 Program Counter Register:線程
衆所周知,虛擬機處理多線程時,是經過輪流的切換線程,來獲取cpu的執行機會的。在虛擬機執行程序的過程當中,當線程執行到某一位置時,虛擬機將cpu的執行機會出讓給了其餘線程,此時原有線程的執行(防盜鏈接:本文首發自http://www.cnblogs.com/jilodream/ )位置須要被記錄下來,而新獲得執行機會的線程,又須要提供上次執行的位置,以此來保證程序中的多個線程能夠持續的並行的執行下去。對象
程序計數器的做用就是將各個線程下次所執行的(字節碼)行號(準確來講是指令的地址)記錄下來,以保證其下次執行時能夠正確的執行。blog
根據程序計數器的做用,咱們能夠知道:
一、每一個線程都在這個區域中都應該擁有一個只爲本身提供服務的程序計數器,它們之間是獨立存儲,互不影響的存在。
二、咱們還能夠知道,程序計數器只記錄字節碼的行號,所以當線程執行本地方法(Native method)時,計數器的值是空。
三、程序計數器所耗費的內存空間很是小,所以這個區域是不會拋出OutOfMemoryError錯誤的。
虛擬機棧 VM Stack:
線程想要正常的運行下去,單靠程序計數器來記錄行號是遠遠不止的。線程還須要擁有本身的運行空間,在這個空間中,虛擬機能夠保存方法的執行順序、方法的內部局部變量,方法在運算時,所須要的內存空間等。
在數據結構中,棧的特性最知足方法的進入返回的結構的。而這塊區域的主(防盜鏈接:本文首發自http://www.cnblogs.com/jilodream/ )要做用就是線程在執行java方法時所須要記錄的數據。所以咱們將這塊區域稱之爲虛擬機棧。可是要記住這裏與咱們在工做中一般指的棧並不等同,這個我會在後邊介紹。
虛擬機棧的結構以下:
而對於每個棧幀內部的劃分又是這樣的:
每一部分的做用以下:
(1)局部變量表:每個方法均可以定義一個只屬於本身的局部變量,當這個方法運行結束後,這個局部變量的生命週期也就宣告結束。因此每個方法都應該擁有一個塊屬於本身的內存區域用來保存方法內部定義的局部變量。這塊區域就是局部變量表,咱們日常工做中所指的棧,實際上指的是虛擬機棧中的棧幀中的局部變量表。
(2)操做數棧:每一個方法的內部均可以計算數據,而計算數據勢必須要擁有一塊內存區域,爲虛擬機用來進行數值計算。所以在棧幀中,就須要有一塊區域專門爲當前方法計算數據使用,它就是操做數棧。
在每進行一次完整的計算以後,棧中的數據都已經出棧,因此操做數棧的空間在一個方法內部是能夠反覆使用的。因此虛擬機在分配內存大小時,只分配當前方法,單次完整計算所須要的最大內存空間給當前棧幀,以減小內存的消耗。
同時爲了增長運行效率,減小數據的不斷複製,在大部分虛擬機的實現中,將當前方法的局部變量表和上層方法的操做數棧的內存造成部分重疊,從而減小參數的不斷複製而引發的性能消費。(防盜鏈接:本文首發自http://www.cnblogs.com/jilodream/ )
(3)動態鏈接:
虛擬機在執行方法時有兩種形式被用來肯定執行指令所對應的方法,
第一種是類加載時,能夠直接肯定要執行的方法,譬如靜態方法,私有方法,final方法等。這種形式叫作靜態解析。
第二種是在真正運行時,根據對象的真實引用來判斷當前真正要執行的方法。這種形式稱之爲動態鏈接。
在字節碼文件中,都存在一個常量池,在這個常量池中保存有大量的符號引用,這個符號引用是每個方法的間接引用。在字節碼指令的中,使用的是這個符號引用。可是在運行時階段,確定須要調用到要執行方法在內存中真實的地址。這就須要將間接引用轉化成直接引用。而這裏的「動態鏈接」就是爲了保證在運行時階段,方法能夠正確的找到要調用的方法,每一個棧幀將本身在運行時常量池中所對應的真實地址記錄的位置。
這裏須要注意的是,在棧幀中的動態鏈接和查找符號引用爲真實引用中的動態鏈接,是兩個概念。前者表示的是一個區域,後者表示的是一種查找方式。
(4)返回地址:
退出當前方法的方式有兩種,第一種是遇到返回指令時,正常的退出當前方法。另外一種形式是遇到沒有捕獲而被拋出的異常。不管何種返回形式,在方法退出後,棧幀的頂端都應是當前退出方法的上層方法。同時上層方法的執行狀態也須要根據當前的返回結果從新調整。因此每一個棧幀能夠利用「返回地址」這塊區域幫助上層方法恢復狀態。
(5)附加信息:對於虛擬機規範中沒有申明的,擁有指定存放位置的信息能夠由各個虛擬機本身決定,放置到這個區域中。
本地方法棧 Native Stack
在虛擬機中,不但運行java方法,還會運行本地方法,也就是常見的Native 關鍵字修飾的方法。在虛擬機棧中,會爲每一個線程獨立的開闢一個專門運行java語言(更準確的說應該是字節碼)的方法(防盜鏈接:本文首發自http://www.cnblogs.com/jilodream/ )棧,可是對於本地方法,則是使用另外的一塊內存區域來保存線程的調用狀態,這塊區域就是本地方法棧。他的做用跟虛擬機棧基本類似,其區別就是一個爲java方法服務,一個爲Native發光法服務。在虛擬機規範中,對於本地方法棧中的結構、方法的語言、方式,都沒有強制規定,各個虛擬機能夠自由的實現它。
Java堆 Java Heap
咱們日常所說的,在堆中建立一個實例,指的就是這個堆。這是虛擬機所管理的內存中最大的一塊。在虛擬機中,幾乎全部的實例以及數組所分配的內存空間都會被放置在這個堆中。
因爲java堆是對象實例的的主要存放位置,所以虛擬機的垃圾回收機制的主要工做區域。
根據Java的內存回收機制,咱們能夠將堆的大小和內容劃分紅以下的形式:
根據java堆的特性,咱們也能夠知道,這塊區域是一塊線程共享的區域。同時咱們也能夠看出來,這塊區域,所可使用在物理上非連續的內存,只要在邏輯上保持連續便可。
方法區 Method Area
方法區的主要做用是保存類信息、常量、靜態變量以及即時編譯後的代碼等數據。這個區域中的數據仍然會被GC的代回收所涉及到。咱們日常所說的永久代,指的就是這個區域。
儘管這個區域也被稱之爲永久代,可是當數據進入這個區域中,仍然可能會被回收。這個區域的回收目標主要是常量池的回收,以及類型的卸載。
運行時常量池 Runtime Constant Pool
這塊區域屬於方法區的中的一塊子區域。
在Class文件中,除了有類版本、字段、方法、接口等,還有一個信息區(防盜鏈接:本文首發自http://www.cnblogs.com/jilodream/ )域是常量池。常量池中的數據將會在類被加載後,存在到運行時常量池中。
而類文件中的常量池主要包括各類字面量和符號引用。符號引用在講解棧幀時,有所涉及。
字面量能夠理解爲java語言中的常量,如字符串、final修飾的變量等。
符號引用則是指如下三種固定信息:
(1)類和接口的全限定名稱
(2)字段的名稱和描述符
(3)方法的名稱和描述符
java語言在編譯成Class文件後,並無關於方法和字段在內存中最終佈局的信息。因此當虛擬機使用這些變量或方法時,須要先從常量池中,找到這些數據對應的符號引用,而後在方法的棧幀中的動態鏈接區域中找到其對應的內存真實位置。
在平常工做中,咱們常常會遇到兩種內存溢出的錯誤:
一、OutOfMemoryError
二、StackOverflowError
OutOfMemoryError指的是一個區域中,因爲數據的不斷增長,致使區域沒法再從物理內存總申請到更大的空間,或者是區域所申請的空間已經到達虛擬機運行參數所給該區域設定的最大值,那麼就會拋出這個錯誤。
StackOverflowError則指的是內存中的棧結構在不斷的入棧,最終致使棧的深度超過了虛擬機所容許的棧深度時,所拋出的錯誤。