- 相關(guān)推薦
Java內存管理原理
Java內存怎么劃分?經(jīng)常有人把Java內存區分為堆內存(Heap)和棧內存(Stack),這種分法比較粗糙,Java內存區域的劃分實(shí)際上遠比這復雜。這種劃分方式的流行只能說(shuō)明大多數程序員最關(guān)注的、與對象內存分配關(guān)系最密切的內存區域是這兩塊。其中所指的“堆”是為 Java 堆,所指的“棧”是為虛擬機;蛘哒f(shuō)是虛擬機棧中局部變量表部分。
Java虛擬機所管理的內存將會(huì )包括以下幾個(gè)運行時(shí)數據區域,如下圖所示:
程序計數器
程序計數器是一塊較小的內存空間,可以看做是當前線(xiàn)程所執行的字節碼的行號指示器。在虛擬機的概念模型里,字節碼解釋器工作時(shí)就是通過(guò)改變這個(gè)計數器的值來(lái)選取下一條需要執行的字節碼指令,分支、循環(huán)、跳轉、異常處理、線(xiàn)程恢復等基礎功能都需要依賴(lài)這個(gè)計數器完成。
由于Java虛擬機的多線(xiàn)程是通過(guò)線(xiàn)程輪流切換并分配處理器執行時(shí)間的方式來(lái)實(shí)現的,在任何一個(gè)確定的時(shí)刻,一個(gè)處理器(對于多核處理器來(lái)說(shuō)是一個(gè)內核)都只會(huì )執行一條線(xiàn)程中的指令。因此,為了線(xiàn)程切換后能恢復到正確的執行位置,獨立存儲,我們稱(chēng)這類(lèi)區域為“線(xiàn)程私有”的內存。
如果線(xiàn)程正在執行的是一個(gè)Java方法,這個(gè)計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是Native方法,這個(gè)計數器值為空(undefined)。
此內存區域是唯一一個(gè)在Java虛擬機規范中沒(méi)有規定任何 OutOfMemoryError 情況的區域。
Java虛擬機棧
與程序計數器一樣,Java 虛擬機棧也是線(xiàn)程私有的,生命周期與線(xiàn)程相同。
虛擬機棧描述的是 Java 方法執行的內存模型:每個(gè)方法在執行的同時(shí)都會(huì )創(chuàng )建一個(gè)棧幀用于存儲局部變量表、操作數棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)方法調用直至執行完成的過(guò)程,就對應著(zhù)一個(gè)棧幀在虛擬機棧中入棧到出棧的過(guò)程。
局部變量表存放了編譯期可知的各種基本數據類(lèi)型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類(lèi)型,它不等同于對象本身,可能是一個(gè)指向對象起始地址的引用指針,也可能是指向一個(gè)代表對象的句柄或其他與此對象相關(guān)的位置)和returnAddress類(lèi)型(指向了一條字節碼指令的地址)。
其中64位長(cháng)度的 long 和 double 類(lèi)型的數據會(huì )占用2 個(gè)局部變量空間(Slot),其余的數據類(lèi)型只占用一個(gè)。局部變量表所需的內存空間在編譯期間完成分配,當進(jìn)入一個(gè)方法時(shí),這個(gè)方法需要在幀中分配多大的局部變空間是完全確定的,在方法運行期間不會(huì )改變局部變量表的大小。
在 Java 虛擬機規范中,對這個(gè)區域規定了兩種異常狀況:如果線(xiàn)程請求的棧深度大于虛擬機所允許的深度,將拋出 StackOverflowError 異常;如果虛擬機?梢詣(dòng)態(tài)擴展,如果擴展時(shí)無(wú)法申請到足夠的內存,就會(huì )拋出 OutOfMemoryError 異常。
本地方法棧
本地方法棧與虛擬機棧所發(fā)揮的作用是非常相似的,區別不過(guò)是虛擬機棧為虛擬機執行 Java 方法服務(wù),而本地方法棧則為虛擬機使用到的 Native 方法服務(wù)。在虛擬機規范中對本地方法棧中的方法使用的語(yǔ)言、使用方法與數據結構并沒(méi)有強制規定,因此具體的虛擬機可以自由實(shí)現它。甚至有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二為一。
與虛擬機棧一樣,本地方法棧區域也會(huì )拋出 StackOverflowError 和 OutOfMemoryError 異常。
Java 堆
對于大多數應用來(lái)說(shuō),Java 堆是 Java 虛擬機所管理的內存中最大的一塊。Java堆是被所有線(xiàn)程共享的一塊內存區域,在虛擬機啟動(dòng)時(shí)創(chuàng )建。此內存區域的唯一目的就是存放對象實(shí)例,幾乎所有的對象實(shí)例以及數組都在這里分配內存。
Java堆是垃圾收集器管理的主要區域,因此很多時(shí)候也被稱(chēng)為“GC堆”。從內存分配的角度看,由于現在收集器基本都采用分代收集算法,所以 Java 堆中還可以細分為:新生代和老年代;再細致一點(diǎn)的有 Eden 空間、From Survivor 空間、To Survivor 空間等。從內存分配的角度看,線(xiàn)程共享的 Java 堆中可能劃分出多個(gè)線(xiàn)程私有的分配緩沖區。不過(guò)無(wú)論如何劃分,都與存放內容無(wú)關(guān),無(wú)論哪個(gè)區域,存儲 的都仍然是對象實(shí)例,進(jìn)一步劃分的目的是為了更好地回收內存,或者更快地分配內存。
Java虛擬機規范的規定,Java 堆可以處于物理上不連續的內存空間中,只要邏輯上是連續的即可。在實(shí)現時(shí),既可以實(shí)現成固定大小的,也可以是可擴展的,不過(guò)當前主流的虛擬機都是按照可擴展來(lái)實(shí)現的(通過(guò)-Xmx和 -Xms控制)。
如果在堆中沒(méi)有內存完成實(shí)例分配,并且堆也無(wú)法再擴展時(shí),會(huì )拋出 OutOfMemoryError 異常。
方法區
方法區與 Java 堆一樣,是各個(gè)線(xiàn)程共享的內存區域,它用于存儲已被虛擬機加載的類(lèi)信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數據。
方法區在虛擬機啟動(dòng)的時(shí)候被創(chuàng )建,雖然方法區是堆的邏輯組成部分,但是簡(jiǎn)單的虛擬機實(shí)現可以選擇在這個(gè)區域不實(shí)現垃圾回收。Java虛擬機規范也不限定實(shí)現方法區的內存位置和編譯代碼的管理策略。方法區的容量可以是固定大小的,也可以隨著(zhù)程序執行的需求動(dòng)態(tài)擴展,并不在過(guò)多空間時(shí)自動(dòng)收縮。方法區在實(shí)際內存空間 中可以是不連續的。
根據 Java 虛擬機規范的規定,當方法區無(wú)法滿(mǎn)足內存分配需求時(shí),將拋出 OutOfMemoryError 異常。
運行時(shí)常量池
運行時(shí)常量池是方法區的一部分,用于存放編譯期生成的各種字面量和符號引用,這部分內容將在類(lèi)加載后進(jìn)入方法區的運行時(shí)常量池中存放。一般來(lái)說(shuō),除了保存Class文件中描述的符號引用外,還會(huì )把翻譯出來(lái)的直接引用也存儲在運行時(shí)常量池中。
運行時(shí)常量池相對于 Class 文件常量池的另一個(gè)重要特征是具備動(dòng)態(tài)性,Java 語(yǔ)言并不要求常量一定只有編譯期才能產(chǎn)生,也就是并非預置入 Class 文件中常量池的內容才能進(jìn)入方法區運行時(shí)常量池,運行期間也可能將新的常量放入池中,這種特性被開(kāi)發(fā)人員利用的比較多的便是 String 類(lèi)的 intern() 方法。
既然運行時(shí)常量池是方法區的一部分,自然受到方法區內存的限制,當常量池無(wú)法再申請到內存時(shí)會(huì )拋出 OutOfMemoryError 異常。
【Java內存管理原理】相關(guān)文章:
電腦內存的工作原理07-10
Java內存溢出的類(lèi)型10-03
Java的內存劃分全解析08-02
JAVA垃圾收集算法與內存泄露的解決方法10-16
Java ClassLoader原理詳細分析201609-23
Linux系統監控內存管理命令大全09-09
項目管理的具體原理與方法09-01