虛擬機在java程序運行的過程當中,會把它所管理的內存劃分紅爲若干個不一樣的數據區。這些不一樣的數據區的做用、建立和銷燬的時候也是不一樣的,有的區域隨着虛擬機的進程的啓動和存在,有的區域則依賴於用戶線程的啓動和結束而創建和銷燬。按照java虛擬機SE7版本的規定,java虛擬機內存區域主要包含以下圖所示的幾個數據區域。java
java虛擬機運行時數據區算法
1.程序計數器數據結構
程序計數器是是一塊由每一個線程獨有的一塊較小的內存空間,是獨立存儲的,線程與線程之間的計數器是互不影響的。當一個線程正在執行一個java方法時,這個線程的程序計數器記錄的是正在執行的虛擬機字節碼指令的地址;若是執行的是native方法,那這個計數器就不記錄任何數據,計數器的值是爲空的。另外,該內存區域是也在java虛擬機規範中惟一一個沒有規定任何OutOfMemoryError(內存溢出)狀況的區域。這個應該很好理解,由於這塊區域中,除了虛擬機字節碼的地址,不會記錄其餘的東西,因此通常也不會發生內存溢出的問題。函數
2.虛擬機棧ui
和程序計數器同樣,虛擬機棧也是由每一個線程獨有的內存空間,它的生命週期和線程是一致的。虛擬機棧是java方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀,用來存儲方法執行過程當中的變量表、操做數棧、動態連接、方法出口等信息。每個方法從調用到執行完成的過程,就對應着一個棧幀在虛擬機中入棧和出棧的過程。若是下圖所示。線程
方法在虛擬機中調用的過程指針
局部表量表:存放了編譯器可知的各類基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用類型(reference類型,它不是對象自己,多是一個指向對象起始地址的引用指針,也可能指向一個表明對象的句柄或者與此對象相關的一個位置)和returnAddress類型(指向了一條字節碼指令的地址,我感受多是對應下一個幀的方法字節碼地址)。對象
另外,在這個區域中java虛擬機規定了兩種異常情況:StackOverflowError(棧溢出異常)和OutOfMemoryError(內存溢出異常)。接口
3.本地方法棧生命週期
本地方法棧和虛擬機棧所發揮的做用是 同樣的,他們之間的區別是虛擬機棧執行的是java代碼,而本地方法棧是爲虛擬機執行Native方法而服務的。在虛擬機規範中,對於本地方法中使用的語言、使用方式與數據結構麼有強制規定,虛擬機能夠根據本身的須要自行實現它。例如,對於咱們熟悉的Sun的HotSpot虛擬機,它直接把本地方法棧和虛擬機棧合二爲一。與虛擬機棧同樣,本地方法棧也有棧溢出和內存溢出。
4.堆
對於平常應用中,java堆一般都是Java虛擬機所管理中所佔內存最大的一塊內存區域。這塊內存區域是被全部的線程所共享的,它的生命週期伴隨着java虛擬機的啓動和關閉而建立和消亡。按照java虛擬機的規範的描述,在這個區域內是存放全部的java對象實例的地方。實際的虛擬機實現上,這個區域也是存放了幾乎全部的java對象實例。值得注意的是,隨着技術的發展,特別是JIT技術(Just-In-Time Compiler:即時編譯技術,它是一個把Java的字節碼(包括須要被解釋的指令的程序)轉換成能夠直接發送給處理器的指令的程序。當你寫好一個Java程序後,源語言的語句將由Java編譯器編譯成字節碼,而不是編譯成與某個特定的處理器硬件平臺對應的指令代碼(好比,Intel的Pentium微處理器或IBM的System/390處理器)。字節碼是能夠發送給任何平臺而且能在那個平臺上運行的獨立於平臺的代碼。)的發展,全部的對象都在堆內存上分配也不是那麼的絕對了。
而咱們經常所說的Java虛擬機的垃圾回收機制一般就是針對這塊區域的,因此這段區域也一般被親切地叫作GC堆。如今GC堆回收算法基本都是採用的分帶回收的算法,這樣作的目的是爲了提升垃圾回收的效率,把java對象實例按照可能的生命的週期分配在不一樣的堆空間,也就是不一樣的代中,這樣就能夠根據不一樣的代而採用不一樣的回收算法,從而提升GC的效率。具體怎麼分代,不一樣的虛擬機能夠有不一樣的實現,簡單能夠分爲新生代和老年代,再詳細點還能夠分爲:Eden代、From Suivive和To Survive等等。
另外,在Java中咱們經常使用的Thread Local(Tread Local Allocation Buffer,TLAB)線程私有內存區域並非在線程私有的虛擬機棧或者是本地方法棧中。實際上,Thread Local也是在堆內存中分配的,只是從對內存中劃分出來部分區域做爲線程私有的。因此,堆內存全部的對象都是線程共享的,起碼從這點來看不是那麼的絕對。
Java對內存區域是邏輯上連續的存儲空間,物理上能夠不連續。在虛擬機實現時,堆的大小能夠是固定的,也能夠是可擴展的,如今基本上主流的虛擬機的堆大小都是可擴展的(能夠經過-Xmx:JVM初始內存和-Xms:JVM最大的內存),當堆中沒有內存完成實例分配,並且堆也沒法再擴展時,將會拋出OutOfMemoryError。
5.方法區
方法區和java堆同樣,是各個線程共享的內存區域,它用於存儲已經被虛擬機加載的類信息 、常量、靜態變量、即時編譯器編譯後的代碼等數據。雖然java虛擬機規範把方法去描述爲堆的一個邏輯部分,可是其實它還有另外一個別名,叫作Non-Heap,目的應該是與java堆區分開。方法區和堆同樣,能夠有物理上不連續可是邏輯上連續的內存空間,能夠選擇固定的內存大小,也能夠選擇擴展內存,另外,它還能夠不實現垃圾回收。跟堆同樣,當方法去沒法知足內存分配的需求時,就會拋出OutOfMemoryError異常。
運行常量池是方法去的一部分,Class文件中除了有類的版本、字段、方法、接口等信息描述外,還有一項信息就是常量池,用於存放編譯期生成的各類字面量和符號引用,這部份內容將在類加載後進入方法區的運行時常量池存放。java並不要求常量必定是編譯期纔會產生的,也就是並不是預置在Class文件中的常量池纔會進入到運行時常量池中,運行期間也能夠產生新的常量放入常量池,這種特性經常使用的就是String類的intern()方法。
6.直接內存
直接內存並非虛擬機運行時內存的一部分,固然也不是java虛擬機規範中定義的內存區域。可是這部分區域在java應用中也是會被頻繁使用的部分,也會致使OutOfMemoryError。在JDK1.4中引入了NIO類,它是一種基於通道(Channel)和緩衝區(Buffer)的IO方式,它可使用Native函數庫在堆外分配內存,而後經過一個存儲在Java堆中的DirectByteBuffer對象做爲這塊內存的引用進行操做。顯然,這種方式的直接內存跟java堆內存沒有直接的關係,因此它的內存空間的大小並不會收到堆內存大小的限制。固然,它仍是會收到本機的總內存以及處理器尋址空間的限制。若是在配置JVM運行時參數時忽略了這部份內存,極有可能發生運行時各個內存區域大小的總和大於物理內存的大小而致使動態擴展時發生OutOfMemoryError異常。
總結:Java虛擬機中大體分爲線程私有和線程共享等兩大內存區域。其中線程私有的內存區域分爲:虛擬機棧、本地方法棧和程序計數器;線程共享的區域可分爲:方法區和堆(嚴格上來講,方法區也是屬於堆內存的一部分,這裏稍微區分開了);另外還有一個不屬於上述內存區域的java直接內存,是在堆以外獨立分配的內存空間,其大小不受堆內存的限制。每一個內存區域的大小、做用和聲明週期各有不一樣。GC機制即垃圾回收主要發生在堆內存區域,堆內存區域在GC時候按照劃分的不一樣區域(即常說的分代)使用不一樣的垃圾回收的算法來提升垃圾回收的效率。