1 概述
對于Java程序員來說在虛擬機自動內存管理機制的幫助下,不需要為每一個new操作去寫配對的delete/free代碼置媳,不容易出現(xiàn)內存泄漏和內存溢出的問題栓始。
2 運行時數(shù)據(jù)區(qū)域。
Java虛擬機在執(zhí)行java程序的過程中會把它所管理的內存劃分為若干個不同的數(shù)據(jù)區(qū)域书释。
由所有現(xiàn)成共享的數(shù)據(jù)區(qū)
- 方法區(qū)
- 堆
線程隔離的數(shù)據(jù)區(qū) - 虛擬機棧
- 本地方法棧
- 程序計數(shù)器
2.1 程序計數(shù)器
? ? 程序計數(shù)器是一塊較小的內存空間翘贮,它可以看做是當前線程所執(zhí)行的字節(jié)碼的行號指示器。在虛擬機的概念模型里征冷,字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令择膝,分支、循環(huán)检激、跳轉肴捉、異常處理、線程恢復等基礎功能都需要依賴這個計數(shù)器來完成叔收。
? ? 由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實現(xiàn)的齿穗。在任何一個確定的時刻,一個處理器都只會執(zhí)行一條線程中的指令饺律。因此窃页,為了線程切換后能恢復到正確的執(zhí)行位置,每條線程都需要有一個獨立的程序計數(shù)器复濒,各條線程之間計數(shù)器互不影響脖卖。獨立存儲。我們稱這類內存區(qū)域為“線程私有”的內存
? ? 如果線程正在執(zhí)行的是一個Java方法巧颈,這個計數(shù)器記錄的正在執(zhí)行的虛擬機字節(jié)碼指令的地址畦木;如果正在執(zhí)行的是Native方法,這個計數(shù)器值則為空砸泛。此內存區(qū)域是唯一一個在Java虛擬機規(guī)范中沒有規(guī)定任何內存溢出情況的區(qū)域
2.2 Java虛擬機棧
? ? 與程序計數(shù)器一樣十籍,Java虛擬機棧也是線程私有的,它的生命周期與線程相同唇礁。虛擬機棧描述的是Java方法執(zhí)行的內存模型:每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀用于存儲局部變量表勾栗、操作數(shù)棧、動態(tài)鏈接盏筐、方法出口等信息围俘。每一個方法從調用直至執(zhí)行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程
? ? 局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型琢融、對象引用和一條字節(jié)碼指令的地址
? ? 其中64位長度的long和double類型的數(shù)據(jù)會占用2個局部變量空間楷拳,其余的數(shù)據(jù)類型只占用1個。局部變量表所需的內存空間在編譯期間完成分配吏奸,當進入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的陶耍,在方法運行期間不會改變局部變量表的大小奋蔚。
? ? 在Java虛擬機規(guī)范中,對這個區(qū)域規(guī)定了兩種異常狀況:如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常:如果虛擬機棽幢可以動態(tài)擴展坤按,如果擴展時無法申請到足夠的內存,就會拋出內存溢出溢出
2.3 本地方法棧
??本地方法棧與虛擬機棧所發(fā)揮的作用是非常相似的馒过,它們之間的區(qū)別不過是虛擬機棧為虛擬機執(zhí)行Java方法服務臭脓,而本地方法棧則為虛擬機使用到的Native方法服務。在虛擬機規(guī)范中對本地方法棧中方法使用的語言腹忽、使用方式與數(shù)據(jù)結構并沒有強制規(guī)定来累,因此具體的虛擬機可以自由實現(xiàn)它。甚至有的虛擬機直接把本地方法棧和虛擬機棧合二為一
2.4 Java堆
??對于大多數(shù)應用來說窘奏,java堆是Java虛擬機所管理的內存中最大的一塊嘹锁。Java對是被所有線程共享的一塊內存區(qū)域,在虛擬機啟動時創(chuàng)建着裹。此內存區(qū)域的唯一目的就是存放對象實例领猾,幾乎所有的對象實例都在這里分配內存。所有的對象實例以及數(shù)組都要在堆上分配骇扇,但是隨著JIT編譯器的發(fā)展與逃逸分析技術逐漸成熟摔竿,棧上分配、標量替換優(yōu)化技術將會導致一些微妙的變化發(fā)生少孝,所有的對象都分配在堆上也漸漸的不是那么絕對继低。
??Java堆是垃圾收集器管理的主要區(qū)域,因此很多時候也被稱作GC堆韭山。從內存回收的角度來看郁季,由于現(xiàn)在收集器基本都采用分代收集算法,所以java堆中還可以細分為:新生代和老年代钱磅。從內存分配的角度來看梦裂,線程共享的Java堆中可能劃分出多個線程私有的分配緩沖區(qū),不過無論如何劃分盖淡,都與存放內容無關年柠,無論哪個區(qū)域,存儲的都仍然是對象實例褪迟。進一步劃分的目的是為了更好的回收內存冗恨,或者更快的分配內存。
??Java堆可以處于物理上不連續(xù)的內存空間中味赃,只要邏輯上是連續(xù)的即可掀抹,就像我們的磁盤空間一樣。在實現(xiàn)時心俗,既可以實現(xiàn)成固定大小的傲武,也可以是可擴展的蓉驹,不過當前主流的虛擬機都是按照可擴展來實現(xiàn)的。如果在堆中沒有內存完成實例分配揪利,并且堆也無法再擴展時态兴,將會拋出異常
2.5 方法區(qū)
方法區(qū)與Java堆一樣,是各個線程共享的內存區(qū)域疟位,它用于存儲已被虛擬機加載的類信息瞻润、常量、靜態(tài)變量甜刻、即時編譯器編譯后的代碼等數(shù)據(jù)绍撞。雖然java虛擬機規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但是它卻有一個別名叫做 非堆罢吃,目的是與Java堆區(qū)分開來.
2.6 運行時常量池
運行時常量池是方法區(qū)的一部分楚午。Class文件中除了有類的版本,字段尿招、方法矾柜、接口等描述信息外,還有一項信息是常量池就谜,用于存放編譯期生成的各種字面量和符號引用怪蔑,這部分內容將在類加載后進入方法區(qū)的運行時常量池中存放。
2.7 直接內存
直接內存并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分丧荐,也不是Java虛擬機規(guī)范中定義的內存區(qū)域缆瓣。但是這部分內存也被頻繁地使用,而且也可能導致異常虹统。
3 對象的創(chuàng)建
虛擬機遇到一條new指令時弓坞,首先將去檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已被加載车荔、解析和初始化過渡冻。如果沒有,那必須執(zhí)行相應的類加載過程忧便。
在類加載檢查通過后族吻,接下來虛擬機將為新生對象分配內存。對象所需內存的大小在類加載完成后便可完全確定珠增,為對象分配空間的任務等同于把一塊確定大小的內存從Java堆中劃分出來超歌。