1. 概述
Java 不像 C/C++ 需要程序員自己管理內存,Java 把內存控制的權利交給類 Java 虛擬機。
2. 運行時數據區域
JVM 在 Java 程序運行時把它所管理的內存劃分爲幾個不同的數據區域。
2.1 程序計數器
程序計數器是一塊較小的內存,它可以看作 ++當前線程++ 所執行的字節碼的行號指示器。在虛擬機的概念模型裏,字節碼解釋器的工作就是通過改變這個 ++計數器的值++ 來選取下一條需要執行的字節碼,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要這個計數器來完成。
如上圖所示,程序計數器是線程私有的。
如何執行工作
如果線程正在執行一個 Java 方法,這個計數器記錄的正是執行的虛擬機字節碼指令的地址,如果執行的是 Native 方法,這個計數器值爲空。
2.2 Java 虛擬機棧
Java 虛擬機棧是線程私有的,其生命週期與線程相同。
虛擬機棧描述的是 Java 方法執行的內存模型: 每個 Java 方法執行的同時都會創建一個 棧幀(stack frame) 用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法從調用到執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。
平時的把 Java 內存分爲 堆內存(heap) 和 棧內存(stack) 的分類方法是比較粗糙的,實際分類方法十分複雜。這種分類方法中棧即爲虛擬機棧,或爲虛擬機中的局部變量表。
局部變量表存放了編譯期可知的各種數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(不等同於對象本身,可能是一個對象起始地址的引用指針,也可能是指向一個代表對象的句柄或其他與此對象相關的位置)和returnAddress(指向一條字節碼指令的地址)。
局部變量表所需的內存空間在編譯期間分配完成,當進入一個方法時,這個方法需要在棧幀中分配多的的局部變量空間是完全確定的,在方法的運行期間不會改變局部變量表的大小。
2.3 本地方法棧
本地方方法棧與虛擬機棧發揮的作用相似,不同的是該棧爲 Native 方法服務。
2.4 Java 堆
Java 堆對於大部分應用來說都是 JVM 所管理的內存中最大的一塊。Java 堆是 所有線程共享 的一塊內存區域,在虛擬機啓動時創建。Java 堆的唯一的目的就是存放對象實例,幾乎所有的對象實例都在這裏分配內存 -- 所有的對象實例以及數組都要在堆上分配。
Java 堆的分配可以在物理上不連續的內存空間,只要邏輯上連續就可以,可以固定大小,也可以擴展空間。
Java 堆是 GC 發生的主要區域。
2.5 方法區
方法區與 Java 堆一樣是所有線程共享的內存空間,++它用於儲存已被虛擬機加載的類信息、常量、靜態常量、即時編譯器編譯後的代碼等數據++。Java 虛擬機把方法區描述爲堆的一個邏輯部分,但是它還有一個別名 -- Non-Heap(非堆),目的應該是與 Java 堆區分。
2.6 運行時常量池
運行區常量池是方法區的一部分。 Class 文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是 常量池 ,用於存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載後進入方法去的運行時常量池中存放。
字面量和符號引用-->常量池-->運行時常量池
2.7 直接內存
沒有接觸過相關概念。
直接內存(Direct Memory)並不是虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域。但是這部分內存也被頻繁地使用,而且也可能導致OutOfMemoryError異常出現,所以我們放到這裏一起講解。
2.8 小結
數據區域 | 功能 | 線程相關 | 備註 |
---|---|---|---|
程序計數器 | 字節碼執行指示器 | 線程私有 | |
Java 虛擬機棧 | 爲 Java方法服務、存放編譯期基本數據類型以及對象引用 | 線程私有 | 平常被稱爲的 "棧"、空間較小 |
本地方法棧 | 與 Java 虛擬機棧類似,爲 Native 方法服務 | 線程私有 | |
Java 堆 | 存放對象(對象實例以及數組) | 線程共享 | GC 主要區域、空間較大 |
方法區 | 存儲類信息、常量、靜態變量、即時編譯器編譯後等數據 | 線程共享 | |
運行時常量池 | 常量池存放編譯期生成的各種字面量和符號引用、常量池在類加載後存儲在運行時常量池 | 線程共享 | 方法區的一部分、常量池會存儲在其中 |
直接內存 |