關注微信公衆號:CodingTechWork,一塊兒學習進步。 java
咱們常常會被問到一個問題是Java和C++有何區別?咱們除了能回答一個是面向對象、一個是面向過程編程之外,咱們還會從底層內存管理和垃圾收集方面做出比較。 對於C++而言,程序員既要作程序設計開發又要維護底層內存管理;而對於Java而言,程序員不須要控制底層,只須要安心寫本身的代碼便可,由於Java虛擬機自動實現了內存管理以及垃圾回收。 可是,咱們寫的程序或者程序環境問題等也時長出現內存泄露和溢出,這個時候程序員若是不知道虛擬機如何分配和管理內存,排查問題將舉步維艱。下面咱們就來一塊兒看看,Java虛擬機內存是如何劃分的。程序員
Java虛擬機在執行Java程序時,會將內存區域劃分爲不一樣的數據區域(運行時數據區)。有些數據區域是跟隨VM進程
的啓動而存在,有的區域是跟隨用戶線程
的啓動和結束而創建和銷燬。編程
通常運行時數據區分爲:程序計數器、虛擬機棧、本地方法棧、Java堆、方法區、運行時常量池。數組
程序計數器(Program Counter Register)是一塊較小的內存空間,是當前線程所執行的字節碼的行號指示器
。字節碼解釋器工做時就是經過改變這個計數器的值來選擇下一條須要執行的字節碼指令(分支、循環、跳轉、異常處理、線程恢復等功能)微信
多線程是經過線程輪換交替並分配處理器執行時間的方式實現,任什麼時候刻,一個處理器都只會執行一條線程的指令,每條線程須要一個獨立的程序計數器,這樣各條線程之間的計數器互不影響,獨立存儲,從而保證線程切換後能夠恢復到正確的執行位置。
反例:線程A和線程B交替執行,線程A計數到5,切換執行,線程B計數到9,若是兩個線程共享程序計數器,則線程A此時計數共享線程B的計數值9,則沒法恢復到正常的執行位置。
正例:線程A和線程B交替執行,線程A計數到5,切換執行,線程B計數到9,線程A再切換執行時,繼續計數到5開始遞增,恢復到正確的執行位置。多線程
此內存區域無OutOfMemoryError異常。函數
若線程正在執行一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;若線程正在執行的Native方法,則計數器值爲空。學習
虛擬機棧(Java Virtual Machine Stacks)是Java方法執行的內存模型,每一個方法在執行的同時都會建立一個棧幀
,用於存儲局部變量表、操做數棧、動態連接、方法出口
等信息。每一個方法的從調用一直到執行完成,都對應着一個棧幀在VM棧中入棧到出棧
的過程。優化
Java虛擬機棧是線程私有的,其生命週期和線程同步,隨着線程的啓動而建立,隨線程的結束而銷燬。線程
Loca Variable Table,虛擬機棧中的局部變量表一般就是所說的虛擬機棧,是一組變量值存儲空間,用於存放方法參數和方法內部定義的局部變量。其中存放了編譯期間可知的基本數據類型、對象引用類型和方法返回地址類型。
局部變量表所佔用的內存空間是在編譯期間就已經分配
好了,進入一個方法時,這個方法須要在幀中分配多大的局部變量空間是肯定的,在方法運行期間也不會去改變這個局部變量表的大小。
基本數據類型:8種基本數據類型是boolean、byte 8位、char、short 16位、int 32位、float 32位、long 64位、double 64位。其中64位長度的long和double類型數據會佔用2個局部變量空間(slot),其他的都是佔用1個slot。
對象引用類型:reference類型,與對象引用不等價,通常是指向對象起始地址的引用指針,或者是指向一個表明對象的句柄、其餘與此對象相關的的位置。
方法返回地址類型:returnAddress類型指向一條字節碼指令的地址。
Operand Stack,操做數棧也稱爲操做棧,是一個後入先出棧,其中村的每一個元素能夠是任意的Java數據類型。當一個方法剛開始執行時,該方法的操做數棧是空的,當方法執行過程當中,會有各類字節碼指令往操做數棧中寫入和提取內容(入棧/出棧操做)
Dynamic Linking,每一個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的應用,持有這個引用就是爲了支持方法調用過程當中的動態連接,常量池中的符號引用在每一次運行期間轉化爲直接引用即爲動態連接。
returnAddress,當一個方法開始執行後,會有兩種方式退出該方法:正常完成出口
和異常完成出口
。
正常完成出口:程序執行過程當中遇到任意一個方法返回的字節碼指令,返回值傳遞給上一層調用者,正常退出程序方法。
異常完成出口:程序方法執行過程當中遇到異常,這個異常沒有在方法體內獲得處理(沒有try...catch,沒有throw異常),致使方法退出。
StackOverflowError異常:線程請求的棧深度大於虛擬機所容許的深度,拋出該異常。
OutOfMemoryError異常:虛擬機能夠動態擴展時,不可以擴展申請到足夠的內存,拋出該異常。
本地方法棧(Native Method Stack)是爲虛擬機使用的Native方法服務
StackOverflowError異常:線程請求的棧深度大於虛擬機所容許的深度,拋出該異常。
OutOfMemoryError異常:虛擬機能夠動態擴展時,不可以擴展申請到足夠的內存,拋出該異常。
異:
虛擬機棧是爲VM執行Java方法(字節碼)服務;本地方法棧是爲VM執行的Native方法。
同:
與虛擬機做用相似,有的VM是將二者合二爲一,都會拋出StackOverFlowError
和OutOfMemoryError
異常。
Java堆(Java Heap)是JVM中最大的一塊內存
,是存放對象實例的區域。全部對象實例以及數組都要在堆上分配
。固然,也有特特殊的狀況,JIT編譯器的發展與逃逸分析技術會促進棧上分配及變量替換優化
技術的發展。
Java堆是被全部線程共享的一塊內存區域,在VM啓動時建立。線程共享能夠將Java堆劃分出多個線程私有的分配緩衝區(Thread Local Allocation Buffer,TLAB)。
Garbage Collected Heap,Java堆是垃圾收集器管理
的主要區域,簡稱GC堆。Java堆細分爲新生代和老年代
,再細分爲Eden
空間、From Survivor
空間、To Survivor
空間。
Java堆能夠處於物理上不連續的內存空間,只須要邏輯連續
便可,有點相似於磁盤空間。實現時,既能夠是固定大小,也能夠擴展(-Xmx和-Xms
參數進行控制)。
若堆中沒有內存完成實例分配,堆也沒法擴展時,會拋出OutOfMemoryError
異常。
方法區(Method Area)用於存儲已被虛擬機加載的類的信息、常量、靜態變量、即時編譯器編譯後的代碼
等數據。
和Java堆同樣,也是線程共享區域。
對於HotSpot虛擬機而言,方法區能夠稱做爲「永久代」或者「持久代」,這是由於HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至了方法區,使用永久代來實現方法區
,垃圾收集器能夠像管理Java堆同樣管理這部份內存區域。
方法區和Java堆同樣不須要連續的物理內存空間,能夠選擇固定大小也能夠選擇擴展,同時,可選擇不實現垃圾收集,方法區永久代中的數據不表明永久存在,該內存區域的內存回收目標是針對常量池的回收和對類型的卸載
。
運行時常量池(Runtime Constant Pool)是方法區的一部分,相比較而言,Class文件
中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池(Constant Pool Table)
,Class文件常量池
用於存放編譯器生成的各類字面量和符號引用
,這部份內容將在類加載後進入方法區的運行時常量池
中存放。
JVM規範對運行時常量池限制比較寬鬆,不一樣的廠商能夠根據本身的需求自行實現該內存區域,通常而言,除了保存Class文件中描述的符號引用外,還會將轉化出的直接引用存儲在其中。
與Class文件常量池相比較而言,運行時常量池還具有動態性
,常量不必定在編譯器產生,即並不是預置入Class文件常量池的內容才能進入方法區運行時常量池,運行期間也能夠將新的常量放入池子
中,如String類的intern()
方法。
沒法申請到內存時,會拋出OutOfMemoryError
異常。
頻繁使用且致使OutOfMemoryError
。NIO(New Input/Output)
類,引入一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,可使用Native函數庫直接分配堆外內存,而後經過一個存儲在Java堆中的DirectByteBuffer對象
做爲這塊內存的引用進行操做(可避免在Java堆和Native堆中來回複製數據
)-Xmx
等參數來設置實際內存大小,在咱們實際操做過程當中,常常忽視直接內存,使得整個內存區域總和大於物理內存限制,致使OutOfMemoryError異常。運行時數據區 | 線程是否私有 | 做用 | 異常 |
---|---|---|---|
程序計數器 | 線程私有 | 每一個線程都有本身的程序計數器,是當前線程所執行的字節碼的行號指示器。 | 無 |
虛擬機棧 | 線程私有 | 是Java方法執行的內存模型,每一個方法在執行的同時都會建立一個棧幀(Stack Frame)用戶存儲局部變量表、操做數棧、動態連接、方法出口等信息。每一個方法從調用直至執行完成的過程,就是對應着一個棧幀在虛擬機棧中入棧和出棧的過程。 | 若線程請求的棧深度大於虛擬機所容許的深度,拋出StackOverflowError異常;若虛擬機能夠動態擴展,而擴展時沒法申請到足夠的內存,就拋出OutOfMemoryError異常 |
本地方法棧 | 線程私有 | 爲虛擬機使用本地(Native)方法服務 | 同虛擬機棧 |
Java堆 | 線程共享 | 在虛擬機啓動時建立,該區域惟一目的是存放對象實例,幾乎全部實例都是在這裏分配內存;gc的主要區域 | 若堆中沒有內存完成實例分配且堆沒法再擴展時,將會拋出OutOfMemoryError異常。 |
方法區 | 線程共享 | 存儲已被VM加載的類信息、常量、靜態變量、即時編譯器後的代碼等數據 | 當方法區沒法知足內存分配需求時,將拋出OutOfMemoryError異常 |
至此,JVM運行時數據區所有總結完畢。
參考《深刻理解Java虛擬機》