JVM的主要結構以下圖所示,圖片引用自舒の隨想日記。 html
方法區和堆由全部線程共享,其餘區域都是線程私有的 java
相似於PC寄存器,是一塊較小的內存區域,經過程序計數器中的值尋找要執行的指令的字節碼,因爲多線程間切換時要恢復每個線程的當前執行位置,因此每一個線程都有本身的程序計算器。這一個區域不會有OutOfMemeryError。當執行Java方法時,這裏存儲的執行的指令的地址,若是執行的是本地方法,這裏的值是Undefined。 算法
虛擬機棧也是線程私有的,每建立一個線程,虛擬機就會爲這個線程建立一個虛擬機棧,虛擬機棧表示Java方法執行的內存模型,每調用一個方法,就會生成一個棧幀(Stack Frame)用於存儲方法的本地變量表、操做棧、方法出口等信息,當這個方法執行完後,就會彈出相應的棧幀。 數組
若是請求的棧的深度過大,虛擬機可能會拋出StackOverflowError異常,若是虛擬機的實現中容許虛擬機棧動態擴展,當內存不足以擴展棧的時候,會拋出OutOfMemoryError異常。 多線程
棧幀分爲三部分:局部變量區(Local Variables)、操做數棧(Operand Stack)和幀數據區(Frame Data)。 函數
局部變量區被組織一個一個從0開始的字數組,byte、short、char在存儲前被轉換爲int,boolean也被轉換爲int,0表示false,非0表示true,long和double佔據兩個字長。 佈局
操做數棧也被組織爲一個字數組,但不一樣於局部變量區,它不是經過數組下標訪問的,而是能過棧的Push和Pop操做,前一個操做Push進的數據能夠被下一個操做Pop出來使用。 spa
這部分的做用主要有三部分: .net
與虛擬機棧相似,只是是執行本地方法時使用的。 線程
用於存儲已被虛擬機加載的類型信息、常量、靜態變量、即時編譯後的代碼等信息。
方法區是線程間共享的,當兩個線程同時須要加載一個類型時,只有一個類會請求ClassLoader加載,另外一個線程會等待。
對於每個加載的類型,會在方法區中保存如下信息:
對於每個字段,會在方法區中保存如下信息(字段聲明順序也會保存):
對於每個方法,會在方法區中保存如下信息(方法聲明順序也會保存):
若是方法不是抽象方法並非本地方法(Native Method),還會保存如下信息:
虛擬機須要存儲一些數據,用來快速地訪問一個類對象中的方法,通常實現爲一個方法表。
方法區中還有一部分是運行時常量池,主要用來存儲編譯時生成的字面量和符號引用,常量也能夠在運行時產生,如String的intern方法。
方法區中也可能存在GC,但虛擬機規範對此不作要求,主要是回收一些常量和卸載一些不用的類型信息,不過要卸載一個類的條件很難達到,並且些處GC其實也回收不了多少內存。
虛擬機中用於存放對象與數組實例的地方,垃圾回收的主要區域就是這裏(還可能有方法區)。
若是垃圾收集算法採用按代收集(目前大都是這樣),這部分還能夠細分爲新生代和老年代。
新生代又可能分爲Eden區,From Survivor區和To Survivor區,主要是爲了垃圾回收。全部的線程共享Java堆,在這裏還能夠劃分線程私有的緩衝區(Thread Local Allocation Buffer,TLAB)。
Java堆只要求邏輯上是連續的,在物理空間上能夠不連續。
JDK1.4中引用了NIO,並引用了Channel與Buffer,可使用Native函數庫直接分配堆外內存,並經過一個存儲在Java堆裏面的DirectByteBuffer對象做爲這塊內存的引用進行操做。
當新建一個對象時,會在堆中爲這個對象分配內存,並在棧中有一個對這個對象引用,除此以外,在Java堆中還要能經過這個對象找到它的類型信息(對象類型,父類,實現的接口,包含的字段與方法等)。
Reference在Java虛擬機中定義爲指向對象的引用,但沒有定義這個Reference應該有怎麼實現。
一種實現是Reference直接存儲對象在堆內的地址,對象的類型信息能夠在對象在堆中的內存佈局中存儲,如存儲在對象內存的開頭等。
另外一種實現是Reference指向一個句柄表中的一個位置,句柄中保存了對象的實際位置及它對應的類型信息。
使用句柄的好處是當在內存中移動對象的位置時,只須要更新句柄表中的內容,不須要改變引用值,但會多一次內存訪問開銷,直接引用的優缺點與此相反。
參考資料: