堆(Heap)
與非堆(Non- heap)
內存依照官方的說法:「Java 虛擬機器有一個堆,堆是運行時資料區域,所有類別實例和陣列的記憶體均從此處分配。
堆是在Java 虛擬機啟動時創建的。」「在JVM中堆之外的記憶體稱為非堆記憶體(Non-heap memory)」。可以看出JVM主要管理兩種類型的記憶體:堆和非堆。
簡單來說堆就是Java程式碼可及的內存,是留給開發人員使用的;非堆就是JVM留給自己用的,所以方法區、JVM內部處理或優化所需的內存(如JIT編譯後的程式碼快取)、每個類別結構(如運行時常數池、欄位和方法資料)以及方法和建構方法的程式碼都在非堆記憶體中。
1.方法區 也稱為”永久代” 、“非堆”, 它用於儲存虛擬機器載入的類別資訊、常數、靜態變數、是各個執行緒共享的記憶體區域。預設最小值為16MB,最大值為64MB,可透過-XX:PermSize
和 -XX:MaxPermSize
參數限制方法區的大小。
運行時常數池:是方法區的一部分,其中的主要內容來自於JVM對Class的載入。
Class檔案中除了有類別的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常數池,用於存放編譯器產生的各種符號引用,這部分內容將在類別載入後放到方法區的運行時常數池中。
2.虛擬機棧
描述的是java 方法執行的記憶體模型:每個方法被執行的時候都會創建一個「棧幀」用於存儲局部變數表(包括參數)、操作棧、方法出口等資訊。每個方法被呼叫到執行完的過程,就對應一個堆疊幀在虛擬機器棧中從入棧到出棧的過程。宣告週期與線程相同,是線程私有的。
局部變數表存放了編譯器可知的各種基本資料型別(boolean
、byte
、char
、short
、int
、float
、long
、double
)、物件引用(引用指針,並非物件本身),其中64位長度的long和double類型的資料會佔用2個局部變數的空間,其餘資料型別只佔1個。
局部變數表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在堆疊幀中分配多大的局部變數是完全確定的,在運行期間棧幀不會改變局部變數表的大小空間。
3.本地方法堆疊
與虛擬機器堆疊基本上類似,差別在於虛擬機器堆疊為虛擬機器執行的java方法服務,而本地方法堆疊則是為Native方法服務。
4.堆
也叫做java 堆、GC堆是java虛擬機所管理的記憶體中最大的一塊記憶體區域,也是被各個執行緒共享的內存區域,在JVM啟動時建立。此記憶體區域存放了物件實例及陣列(所有new的物件)。
其大小透過-Xms
(最小值)和-Xmx
(最大值)參數設置,-Xms為JVM啟動時申請的最小內存,預設為作業系統物理記憶體的1/64但小於1G,-Xmx
為JVM可申請的最大內存,預設為物理記憶體的1/4但小於1G,預設當空餘堆記憶體小於40%時, JVM會增加Heap到-Xmx
指定的大小,可透過-XX:MinHeapFreeRation=
來指定這個比列;當空堆記憶體大於70%時,JVM會減少heap的大小到-Xms指定的大小,可透過XX:MaxHeapFreeRation=
來指定這個比列,對於運行系統,為避免在運行時頻繁調整Heap的大小,通常-Xms與-Xmx的值設成一樣。
由於現在收集器都是採用分代收集演算法,因此堆被分割為新生代和老年代。新生代主要儲存新創建的物件和尚未進入老年代的物件。老年代存放經過多次新生代GC(Minor GC)任然存活的對象。
新生代: 程式新建立的物件都是從新生代分配內存,新生代由
Eden Space
和兩塊相同大小的Survivor Space
(通常又稱S0和S1或From和To)構成,可透過-Xmn參數來指定新生代的大小,也可以透過-XX:SurvivorRation
來調整Eden Space
及Survivor Space
的大小。老年代: 用於存放經過多次新生代GC任然存活的對象,例如緩存對象,新建的對像也有可能直接進入老年代,主要有兩種情況:
1、大對象,可透過啟動參數設定
-XX:PretenureSizeThreshold=1024
(單位為位元組,預設為0)來代表超過多大時就不在新生代分配,而是直接在老年代分配。2、大的數組對象,切數組中無引用外部對象。老年代所佔的記憶體大小為-Xmx對應的值減去-Xmn對應的值。
Young Generation 即图中的Eden + From Space + To Space Eden 存放新生的对象 Survivor Space 有两个,存放每次垃圾回收后存活的对象 Old Generation Tenured Generation 即图中的Old Space 主要存放应用程序中生命周期长的存活对象
#5.程式計數器
是最小的一塊記憶體區域,它的作用是當前執行緒所執行的字節碼的行號指示器,在虛擬機器的模型裡,字節碼解釋器工作時就是透過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、異常處理、執行緒恢復等基礎功能都需要依賴計數器來完成。
直接記憶體並不是虛擬機器記憶體的一部分,也不是Java虛擬機器規格中定義的記憶體區。 jdk1.4中新加入的NIO,引入了通道與緩衝區的IO方式,它可以呼叫Native方法直接分配堆外內存,這個堆外內存就是本機內存,不會影響到堆內存的大小。
Java堆記憶體是作業系統分配給JVM的記憶體的一部分。
當我們建立物件時,它們儲存在Java堆記憶體中.
為了方便垃圾回收,Java堆空間分成三個區域,分別叫作New Generation, Old Generation或叫作Tenured Generation,還有Perm Space。
你可以用JVM的命令列選項 -Xms, -Xmx, -Xmn來調整Java堆空間的大小。不要忘了在大小後面加上”M”或”G”來表示單位。舉個例子,你可以用 -Xmx256m來設定堆記憶體最大的大小為256MB。
你可以用JConsole或 Runtime.maxMemory(), Runtime.totalMemory(), Runtime.freeMemory()來查看Java中堆記憶體的大小。
你可以使用指令「jmap」來取得heap dump,用「jhat」來分析heap dump。
Java堆疊空間不同於堆疊空間,堆疊空間是用來儲存呼叫堆疊和局部變數的。
Java垃圾回收器是用來將死掉的物件(不再使用的物件)所佔用的記憶體回收回來,再釋放到Java堆空間中。
當你遇到java.lang.outOfMemoryError時,不要緊張,有時候僅僅增加堆空間就可以了,但如果經常出現的話,就要看看Java程式中是不是存在記憶體洩漏了。
- 請使用Profiler和Heap dump分析工具來查看Java堆空間,可以查看給每個物件分配了多少記憶體。
更多相關知識請關注java基礎教學專欄
#以上是JVM的內部結構及運作機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!