深入理解jvm--自動內存管理機制

JVM平臺無關性:



jvm運行時內存區域:



程序計數器(Program Counter Register):

1.在IDE上編譯的Java代碼運行時都會被轉譯成字節碼。程序計數器的就是給編譯好的字節碼添加行號,這樣這些字節碼就以程序計數器的編號來作爲調度時候的標識了。

2.在程序運行時,諸如循環,跳轉,異常處理這些功能都必須依賴於字節碼來完。

我的理解:字節碼是二進制文件,所以識別起來很難,代表一個功能的字節碼數量巨大。如果在編譯的時候就將其在程序計數器上進行編號,則後期調用的時候就可以按照程序員在IDE上用高級語言編譯時候的順序進行分條執行了。

3.現在的計算機大多實現了多線程併發操作,但是一個CPU在某個時間段內只能處理一個線程,多線程併發是依賴於線程輪流切換並給線程分配執行的時間來實現的。

我的理解:
IDE在將高級語言編譯的代碼轉譯成字節碼時,給每個線程都分配有程序計數器。各個線程的程序計數器是相互獨立的,這樣才能保證一個線程內的程序運行不會出錯。因此這塊用於存放程序計數器的內存空間就是這個線程私有化的內存空間。



虛擬機棧(Java Stack)

虛擬機棧也是線程私有的,每創建一個線程,虛擬機就會爲這個線程創建一個虛擬機棧,虛擬機棧表示Java方法執行的內存模型,每調用一個方法,就會生成一個棧幀(Stack Frame)用於存儲方法的本地變量表、操作棧、方法出口等信息,當這個方法執行完後,就會彈出相應的棧幀。

如果請求的棧的深度過大,虛擬機可能會拋出StackOverflowError異常,如果虛擬機的實現中允許虛擬機棧動態擴展,當內存不足以擴展棧的時候,會拋出OutOfMemoryError異常。


棧幀(Stack Frame):


棧幀分爲三部分:局部變量區(Local Variables)、操作數棧(Operand Stack)和幀數據區(Frame Data)。

局部變量區(Loca Variables)

局部變量區被組織一個一個從0開始的字數組,byte、short、char在存儲前被轉換爲int,boolean也被轉換爲int,0表示false,非0表示true,long和double佔據兩個字長。

操作數棧(Operand Stack)

操作數棧也被組織爲一個字數組,但不同於局部變量區,它不是通過數組下標訪問的,而是能過棧的Push和Pop操作,前一個操作Push進的數據可以被下一個操作Pop出來使用。

幀數據區(Frame Data)

這部分的作用主要有三部分:

  • 常量池中數據的解析
  • 方法執行完後處理方法返回,恢復調用方現場
  • 方法執行過程中拋出異常時的異常處理,存儲有一個異常表,當出現異常時虛擬機查找相應的異常表看是否有對應的Catch語句,如果沒有就拋出異常終止這個方法調用

本地方法棧(Native Method Stack)

與虛擬機棧類似,只是是執行本地方法時使用的


方法區(Method Area)

用於存儲已被虛擬機加載的類型信息、常量、靜態變量、即時編譯後的代碼等信息。

方法區是線程間共享的,當兩個線程同時需要加載一個類型時,只有一個類會請求ClassLoader加載,另一個線程會等待。

對於每一個加載的類型,會在方法區中保存以下信息:

  • 類及其父類的全限定名(java.lang.Object沒有父類)
  • 類的類型(Class or Interface)
  • 訪問修飾符(public, abstract, final)
  • 實現的接口的全限定名的列表
  • 常量池
  • 字段信息
  • 方法信息
  • 除常量外的靜態變量
  • ClassLoader引用
  • Class引用

對於每一個字段,會在方法區中保存以下信息(字段聲明順序也會保存):

  • 字段名
  • 字段的類型
  • 字段的修飾符(public, private , protected, static, final, volatile, transient)

對於每一個方法,會在方法區中保存以下信息(方法聲明順序也會保存):

  • 方法名
  • 方法返回類型(或void)
  • 參數信息
  • 方法修飾符(public, private, protected , static, final, synchronized, native, abstract)

如果方法不是抽象方法並不是本地方法(Native Method),還會保存以下信息:

  • 方法的字節碼
  • 本地變量表及操作數棧的大小
  • 異常表

虛擬機需要存儲一些數據,用來快速地訪問一個類對象中的方法,一般實現爲一個方法表。

方法區中還有一部分是運行時常量池,主要用來存儲編譯時生成的字面量和符號引用,常量也可以在運行時產生,如String的intern方法。

方法區中也可能存在GC,但虛擬機規範對此不做要求,主要是回收一些常量和卸載一些不用的類型信息,不過要卸載一個類的條件很難達到,而且些處GC其實也回收不了多少內存。

堆(Heap)

虛擬機中用於存放對象與數組實例的地方,垃圾回收的主要區域就是這裏(還可能有方法區)。

如果垃圾收集算法採用按代收集(目前大都是這樣),這部分還可以細分爲新生代和老年代。

新生代又可能分爲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指向一個句柄表中的一個位置,句柄中保存了對象的實際位置及它對應的類型信息。

使用句柄的好處是當在內存中移動對象的位置時,只需要更新句柄表中的內容,不需要改變引用值,但會多一次內存訪問開銷,直接引用的優缺點與此相反
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章