JVM内存区域详解
Java中虛擬機(jī)在執(zhí)行Java程序的過程中會(huì)將它所管理的內(nèi)存區(qū)域劃分為若干不同的數(shù)據(jù)區(qū)域。下面來介紹幾個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)域。
一、程序計(jì)數(shù)器
1.1 簡述
程序計(jì)數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,它的作用可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。
1.2 作用
大家都知道,Java程序從源文件創(chuàng)建到程序運(yùn)行要經(jīng)過兩大步驟: 1. 源文件由編譯器編譯成字節(jié)碼(ByteCode)。 2. 字節(jié)碼由java虛擬機(jī)解釋運(yùn)行。
java程序編譯運(yùn)行過程
字節(jié)碼解釋器工作時(shí)就是通過改變這個(gè) 程序計(jì)數(shù)器 的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)數(shù)器來完成。
1.3 特性
1.3.1 線程私有情況
由于Java虛擬機(jī)的多線程是通過線程輪流切換并分配處理器執(zhí)行時(shí)間的方式來實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)刻,一個(gè)處理器(對(duì)于多核處理器來說是一個(gè)內(nèi)核)只會(huì)執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各條線程之間的計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ),我們稱這類內(nèi)存區(qū)域?yàn)椤?strong>線程私有”的內(nèi)存。
1.3.2 異常情況
此內(nèi)存區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。
二、Java虛擬機(jī)棧
2.1 描述
大家經(jīng)常說的棧內(nèi)存(Stack)就是指的虛擬機(jī)棧。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame),用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈表、方法出口等信息。
不同的線程使用不同的棧
每一個(gè)方法從調(diào)用直至執(zhí)行完成的過程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中出棧入棧的過程。
2.2 組成
2.2.1 局部變量表
局部變量表是一組變量值存儲(chǔ)空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。它可以存儲(chǔ)各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對(duì)象引用(reference類型)和returnAddress。 1. reference類型:虛擬機(jī)規(guī)范沒有明確說明它的長度。它不等同于對(duì)象本身,根據(jù)虛擬機(jī)種類的不同,可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔?#xff0c;也可能是指向一個(gè)代表對(duì)象的句柄或其他與此對(duì)象相關(guān)的位置。 2. returnAddress:指向了一條字節(jié)碼指令的地址,它是為字節(jié)碼指令jsr、jsr_w和ret服務(wù)的。目前returnAddress類型已經(jīng)很少見了,很古老的java虛擬機(jī)曾經(jīng)使用這幾條指令來實(shí)現(xiàn)異常處理,現(xiàn)在已經(jīng)由異常表代替。
注意: 系統(tǒng)不會(huì)為局部變量賦予初始值(實(shí)例變量和類變量都會(huì)被賦予初始值)。也就是說不存在類變量那樣的準(zhǔn)備階段。
2.2.2 操作數(shù)棧
操作數(shù)棧中存放方法執(zhí)行時(shí)的一些中間變量,JVM 在執(zhí)行方法時(shí)壓入或者彈出這些變量。其實(shí),操作數(shù)棧是方法真正工作的地方,執(zhí)行方法時(shí),局部變量數(shù)組與操作數(shù)棧根據(jù)方法定義進(jìn)行數(shù)據(jù)交換。 例如,執(zhí)行以下代碼時(shí),它演示了虛擬機(jī)是如何把兩個(gè)int類型的局部變量相加,再把結(jié)果保存到第三個(gè)局部變量的。操作數(shù)棧的情況如下:
int a = 90; int b = 10; int c = a + b;字節(jié)碼執(zhí)行過程:
begin iload_0 // push the int in local variable 0 onto the stack iload_1 // push the int in local variable 1 onto the stack iadd // pop two ints, add them, push result istore_2 // pop int, store into local variable 2 end在這個(gè)字節(jié)碼序列里,前兩個(gè)指令iload_0和iload_1將存儲(chǔ)在局部變量中索引為0和1的整數(shù)壓入操作數(shù)棧中,其后iadd指令從操作數(shù)棧中彈出那兩個(gè)整數(shù)相加,再將結(jié)果壓入操作數(shù)棧。第四條指令istore_2則從操作數(shù)棧中彈出結(jié)果,并把它存儲(chǔ)到局部變量區(qū)索引為2的位置。下圖詳細(xì)表述了這個(gè)過程中局部變量和操作數(shù)棧的狀態(tài)變化,圖中沒有使用的局部變量區(qū)和操作數(shù)棧區(qū)域以空白表示。
注意在這個(gè)圖中,操作數(shù)棧的頂部是在底邊,所以先壓入的100位于上方。可以看出,操作數(shù)棧其實(shí)是一個(gè)數(shù)據(jù)臨時(shí)存儲(chǔ)區(qū),存放一些中間變量,方法結(jié)束了,操作數(shù)棧也就沒有啦。
2.2.3 動(dòng)態(tài)鏈表
略,待續(xù)
2.3 特性
2.3.1 線程私有情況
與程序計(jì)數(shù)器一樣,Java 虛擬機(jī)棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同
2.3.2 異常情況
三、本地方法棧
3.1 描述
本地方法棧(Native Method Stack)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,其 區(qū)別 不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機(jī)使用到的Native方法服務(wù)。 虛擬機(jī)規(guī)范中對(duì)本地方法棧中的方法使用的語言、使用方式與數(shù)據(jù)結(jié)構(gòu)并沒有強(qiáng)制規(guī)定,因此具體的虛擬機(jī)可以自由實(shí)現(xiàn)它。甚至有的虛擬機(jī)(譬如Sun HotSpot虛擬機(jī))直接就把本地方法棧和虛擬機(jī)棧合二為一。
3.2 特性
3.2.1 線程私有情況
和虛擬機(jī)棧一樣。
3.2.2 異常情況
與虛擬機(jī)棧一樣,本地方法棧區(qū)域也會(huì)拋出StackOverflowError和OutOfMemoryError異常。
四、Java堆
4.1 描述
對(duì)于大多數(shù)應(yīng)用來說,Java堆(Java Heap)是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。
4.2 作用
4.3 特性
4.3.1 線程私有情況
Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,可以被線程之間共享。
4.3.2 異常情況
Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可,就像我們的磁盤空間一樣。在實(shí)現(xiàn)時(shí),既可以實(shí)現(xiàn)成固定大小的,也可以是可擴(kuò)展的,不過當(dāng)前主流的虛擬機(jī)都是按照可擴(kuò)展來實(shí)現(xiàn)的(通過-Xmx和-Xms控制)。 如果在堆中沒有內(nèi)存完成實(shí)例分配,并且堆也無法再擴(kuò)展時(shí),將會(huì)拋出`OutOfMemoryError`異常。
五、方法區(qū)
5.1 描述
同 Java 堆一樣,方法區(qū)也是全局共享的一塊內(nèi)存區(qū)域。
5.2 作用
5.3 特性
5.3.1 線程私有情況
與Java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域。
5.3.2 異常情況
與java堆類似,不需要連續(xù)的內(nèi)存和可以選擇固定大小或可拓展外,還可以選擇不實(shí)現(xiàn)垃圾回收。 根據(jù)虛擬機(jī)規(guī)范的規(guī)定,當(dāng)方法區(qū)無法滿足內(nèi)存分配的需求時(shí),將拋出OutOfMemoryError異常。
5.4 運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池(Runtime Constant Pool)也是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號(hào)引用,這部分將在類加載后進(jìn)入方法區(qū)運(yùn)行時(shí)常量池中存放。
5.4.2 特性
六、直接內(nèi)存
6.1 描述
直接內(nèi)存(Direct Memory)并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,它指的是主機(jī)的物理內(nèi)存。之所以拿到這里來說,是因?yàn)榭赡軙?huì)導(dǎo)致OutOfMemoryError異常出現(xiàn),所以放到這里一并講解。
6.2 如何使用?
那么什么時(shí)候會(huì)直接使用到物理內(nèi)存呢? 在NIO(New Input/Output)類中,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/0方式,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個(gè)存儲(chǔ)在Java堆中的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。這樣在一些場景中顯著提高性能,因?yàn)楸苊饬嗽贘ava堆和Native堆中來回復(fù)制數(shù)據(jù)。
6.3 異常情況
當(dāng)各個(gè)內(nèi)存區(qū)域總和大于物理內(nèi)存限制(包括物理的和操作系統(tǒng)級(jí)的限制),從而導(dǎo)致動(dòng)態(tài)擴(kuò)展時(shí)出現(xiàn)OutOfMemoryError異常。
七、總結(jié)
最后,讓我們通過表格來大致回顧一下Java虛擬機(jī)中內(nèi)存的特性吧!
| 程序計(jì)數(shù)器 | 線程私有 | 唯一一個(gè)沒有異常情況的區(qū)域 | 當(dāng)前線程所執(zhí)行字節(jié)碼的行號(hào)指示器 |
| Java虛擬機(jī)棧 | 線程私有 | StackOverflowError或OutOfMemoryError異常 | 用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈表、方法出口等信息,為虛擬機(jī)棧執(zhí)行Java方法服務(wù) |
| 本地方法棧 | 線程私有 | StackOverflowError或OutOfMemoryError異常 | 為虛擬機(jī)使用到的Native方法(字節(jié)碼)服務(wù) |
| Java堆 | 線程共享 | OutOfMemoryError異常 | 存放對(duì)象實(shí)例和主要的垃圾回收區(qū)域 |
| 方法區(qū) | 線程共享 | OutOfMemoryError異常 | 用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。和Java堆有相似特性,但不是垃圾回收的主要區(qū)域 |
| 運(yùn)行時(shí)常量池 | 線程共享 | OutOfMemoryError異常 | 方法區(qū)的一部分,和方法區(qū)特性類似。保存了Class文件中描述的符號(hào)引用和翻譯出來的直接引用,同時(shí)具有動(dòng)態(tài)性。 |
| 直接內(nèi)存 | 線程共享 | OutOfMemoryError異常 | 受限于物理內(nèi)存,NIO流可以直接操縱用以提高性能 |
參考鏈接: 1. 棧幀、局部變量表、操作數(shù)棧 2. Java編譯器、JVM、解釋器 3. Java程序編譯和運(yùn)行的過程
總結(jié)
- 上一篇: 45岁没有交过养老保险,是一次性补缴好还
- 下一篇: 信用卡可以贷款买房吗 信用卡贷款买房划算