1. 程式人生 > >jvm--內存區域與內存溢出異常

jvm--內存區域與內存溢出異常

數組 服務 引用 句柄 狀態 native http 生成 tla

java是一門內存動態分配、垃圾自動回收的高級編程語言。

1 運行時數據分區

技術分享

  • 方法區

    用來存儲已被虛擬機加載的類信息、常亮、靜態變量、即時編譯後的代碼等數據;在hotspot虛擬機中又被稱為永久代,此外字符串常量池已經在java7版本後移除永久代。

    運行時常量池是方法區的一部分,具有動態性,用於存放編譯器生成的各種字面量和符號引用。

    內存中最大的一塊,用於存放對象實例,是垃圾回收的主要區域;可分為新生代、老年代,也可分為Eden空間、From Survivor空間、ToSurvivor空間。

  • 虛擬機棧

    為虛擬機執行java方法服務,描述java方法執行的內存模型,每個方法執行的同時都會創建一個棧幀用於存儲局部變量表、操作數棧、動態鏈接、方法出口燈信息;方法的調用到執行完成對應的就是入棧到出棧的過程。

  • 本地方法棧

    為虛擬機使用到的Native方法服務;常用的hotspot虛擬機將虛擬機棧和本地方法棧合為一。

  • 程序計數器

    當前線程所執行的字節碼的行號指示器。

2 HotSpot虛擬機對象探秘

  2.1 對象的創建

    假設類加載步驟已完成,還需要如下過程:

    • 為新生對象分配內存  
    • 將分配的內存空間初始化為零值,不包括對象頭
    • 虛擬機對對象進行必要的設置,如屬於哪個實例、GC分代年齡等信息
    • 至此從虛擬機角度,對象已創建完成;但對java程序而言,還需執行<init>方法

    其中為新生對象分配內存有兩種方式:其一,指針碰撞,這種情況要求堆內存絕對規整,用過的和空閑的分別存放,只需指針在空閑空間移動相應大小即可;其二,空閑列表,虛擬機維護一個記錄內存狀態的列表,分配的時候找到一塊足夠大的空間劃分給對象實例,並更新記錄;兩種方案的選擇由堆是否規整決定,堆的狀態又由具體的垃圾收集器絕定。

    此外,對象創建是一個非常頻繁的動作,需要采取措施保證線程安全。此處同樣有兩種方案可選,其一,同步分配空間,采用CAS配上失敗重試的方式保證原子性操作;其二,分配動作按照線程在不同的空間進行劃分,每個線程在堆中預先分配一小塊內存( Thread Local Allocation Buffer,TLAB),只有當TLAB用完並分配新的TLAB時才需要同步鎖。

  2.2 對象的內存布局

    在HotSpot虛擬機中,對象在內存中分為三個區域:對象頭、實例數據和對齊填充。

    • 對象頭分為兩部分:其一,Mark Word,用於存儲對象自身的運行時數據,如哈希碼、GC分代年齡、鎖狀態標識等;其二,類型指針,指向對象的類元數據的指針,用來確定對象是哪個類的實例。如果是java數組,對象頭中還有一塊記錄數組長度的數據,因為從數組的元數據無法確定數組的大小。
    • 實例數據,存儲代碼中所定義的各種類型的字段內容,包括父類繼承的、子類自定義的。
    • 對齊填充,並不是必然存在,因為HotSpotVM要求對象的起始地址必須是8字節的整數倍,所以部分情況需要補齊。

  2.3 對象的訪問定位

    建立對象是為了使用對象,java程序需要通過棧上的reference數據來操作對上的具體對象,目前主流方式有使用句柄、直接指針兩種方式。

    • 使用句柄,優勢是reference內存儲的是穩定的句柄,在對象被移動時只改變句柄中的實例數據指針,而reference本身不需要改變。

      技術分享

    • 直接指針,優勢是速度快,他節省了一次指針定位的時間開銷,由於對象的訪問非常頻繁,這種方式可以提升很高效率,HotSpot采用這種方式

      技術分享

3 OutOfMemoryError異常

  • java堆溢出,拋OutOfMemoryError異常,後面提示java heap space
  • 棧溢出,如果線程請求的棧深度大於虛擬機所允許的最大深度,拋StackOverflowError異常;如果虛擬機在擴展棧時無法獲取足夠空間,拋OutOfMemoryError異常
  • 方法區和運行時常量溢出,拋OutOfMemoryError異常,後面提示PermGen space

jvm--內存區域與內存溢出異常