Java 記憶體地區與記憶體溢出,java記憶體地區溢出

來源:互聯網
上載者:User

Java 記憶體地區與記憶體溢出,java記憶體地區溢出
記憶體地區

JAVA 虛擬機器在執行 Java 程式的過程中會把他所管理的記憶體劃分為若干個不同的資料區域。JAVA 虛擬機器規範將 JVM 所管理的記憶體分為以下幾個運行時資料區:程式計數器、JAVA 虛擬機器棧、本地方法棧、Java 堆、方法區。下面詳細闡述各資料區所儲存的資料類型。

程式計數器

一塊較小的記憶體空間,它是當前線程所執行的位元組碼的行號指標,位元組碼解譯器工作時通過改變該計數器的值來選擇下一條需要執行的位元組碼指令,分支、跳轉、迴圈等基礎功能都要依賴它來實現。每條線程都有一個獨立的的程式計數器,各線程間的計數器互不影響,因此該地區是線程私人的。

當線程在執行一個 Java 方法時,該計數器記錄的是正在執行的虛擬機器位元組碼指令的地址,當線程在執行的是 Native 方法(調用本地作業系統方法)時,該計數器的值為空白。另外,該記憶體地區是唯一一個在 JAVA 虛擬機器規範中麼有規定任何 OOM(記憶體溢出:OutOfMemoryError)情況的地區。

JAVA 虛擬機器棧

該地區也是線程私人的,它的生命週期也與線程相同。虛擬機器棧描述的是 Java 方法執行的記憶體模型:每個方法被執行的時候都會同時建立一個棧幀,棧它是用於支援續虛擬機器進行方法調用和方法執行的資料結構。對於執行引擎來講,活動線程中,只有棧頂的棧幀是有效,稱為當前棧幀,這個棧幀所關聯的方法稱為當前方法,執行引擎所啟動並執行所有位元組碼指令都只針對當前棧幀進行操作。棧幀用於儲存局部變數表、運算元棧、動態連結、方法返回地址和一些額外的附加資訊。在編譯器代碼時,棧幀中需要多大的局部變數表、多深的運算元棧都已經完全確定了,並且寫入了方法表的 Code 屬性之中。因此,一個棧幀需要分配多少記憶體,不會受到程式運行期變數資料的影響,而僅僅取決於具體的虛擬機器實現。

在 JAVA 虛擬機器規範中,對這個地區規定了兩種異常情況:

  • 如果線程請求的棧深度大於虛擬機器所允許的深度,將拋出StackOverflowError異常。
  • 如果虛擬機器在動態擴充棧時無法申請到足夠的記憶體空間,則拋出OutOfMemoryError異常。

這兩種情況存在著一些互相重疊的地方:當棧空間無法繼續分配時,到底是記憶體太小,還是已使用的棧空間太大,其本質上只是對同一件事情的兩種描述而已。在單線程的操作中,無論是由於棧幀太大,還是虛擬機器棧空間太小,當棧空間無法分配時,虛擬機器拋出的都是 StackOverflowError 異常,而不會得到 OutOfMemoryError 異常。而在多線程環境下,則會拋出 OutOfMemoryError 異常。

下面詳細說明棧幀中所存放的各部分資訊的作用和資料結構。

1、局部變數表

局部變數表是一組變數值儲存空間,用於存放方法參數和方法內部定義的局部變數,其中存放的資料的類型是編譯期可知的各種基礎資料型別 (Elementary Data Type)、對象引用(reference)和 returnAddress 類型(它指向了一條位元組碼指令的地址)。局部變數表所需的記憶體空間在編譯期間完成分配,即在 Java 程式被編譯成 Class 檔案時,就確定了所需分配的最大局部變數表的容量。當進入一個方法時,這個方法需要在棧中分配多大的局部變數空間是完全確定的,在方法運行期間不會改變局部變數表的大小。

局部變數表的容量以變數槽(Slot)為最小單位。在虛擬機器規範中並沒有明確指明一個 Slot 應佔用的記憶體空間大小(允許其隨著處理器、作業系統或虛擬機器的不同而發生變化),一個 Slot 可以存放一個32位以內的資料類型:boolean、byte、char、short、int、float、reference 和 returnAddresss。reference 是對象的參考型別,returnAddress 是為位元組指令服務的,它執行了一條位元組碼指令的地址。對於 64 位元的資料類型(long和double),虛擬機器會以高位在前的方式為其分配兩個連續的 Slot 空間。

虛擬機器通過索引定位的方式使用局部變數表,索引值的範圍是從 0 開始到局部變數表最大的 Slot 數量,對於 32 位元據類型的變數,索引 n 代表第 n 個 Slot,對於 64 位元的,索引 n 代表第 n 和第 n+1 兩個 Slot。

在方法執行時,虛擬機器是使用局部變數表來完成參數值到參數變數列表的傳遞過程的,如果是執行個體方法(非static),則局部變數表中的第 0 位索引的 Slot 預設是用於傳遞方法所屬對象執行個體的引用,在方法中可以通過關鍵字“this”來訪問這個隱含的參數。其餘參數則按照參數表的順序來排列,佔用從1開始的局部變數 Slot,參數表分配完畢後,再根據方法體內部定義的變數順序和範圍分配其餘的 Slot。

局部變數表中的 Slot 是可重用的,方法體中定義的變數,範圍並不一定會覆蓋整個方法體,如果當前位元組碼PC計數器的值已經超過了某個變數的範圍,那麼這個變數對應的 Slot 就可以交給其他變數使用。這樣的設計不僅僅是為了節省空間的,在某些情況下 Slot 的複用會直接影響到系統的而垃圾收集行為。

2、運算元棧

運算元棧又常被稱為操作棧,運算元棧的最大深度也是在編譯的時候就確定了。32 位元據類型所佔的棧容量為 1,64 位元資料類型所佔的棧容量為 2。當一個方法開始執行時,它的操作棧是空的,在方法的執行過程中,會有各種位元組碼指令(比如:加操作、賦值元算等)向操作棧中寫入和提取內容,也就是入棧和出棧操作。

JAVA 虛擬機器的解釋執行引擎稱為“基於棧的執行引擎”,其中所指的“棧”就是運算元棧。因此我們也稱 JAVA 虛擬機器是基於棧的,這點不同於 Android 虛擬機器,Android 虛擬機器是基於寄存器的。

基於棧的指令集最主要的優點是可移植性強,主要的缺點是執行速度相對會慢些;而由於寄存器由硬體直接提供,所以基於寄存器指令集最主要的優點是執行速度快,主要的缺點是可移植性差。

3、動態串連

每個棧幀都包含一個指向運行時常量池(在方法區中,後面介紹)中該棧幀所屬方法的引用,持有這個引用是為了支援方法調用過程中的動態串連。Class 檔案的常量池中存在有大量的符號引用,位元組碼中的方法調用指令就以常量池中指向方法的符號引用為參數。這些符號引用,一部分會在類載入階段或第一次使用的時候轉化為直接引用(如 final、static 域等),稱為靜態解析,另一部分將在每一次的運行期間轉化為直接引用,這部分稱為動態串連。

4、方法返回地址

當一個方法被執行後,有兩種方式退出該方法:執行引擎遇到了任意一個方法返回的位元組碼指令或遇到了異常,並且該異常沒有在方法體內得到處理。無論採用何種退出方式,在方法退出之後,都需要返回到方法被調用的位置,程式才能繼續執行。方法返回時可能需要在棧幀中儲存一些資訊,用來協助恢複它的上層方法的執行狀態。一般來說,方法正常退出時,調用者的 PC 計數器的值就可以作為返回地址,棧幀中很可能儲存了這個計數器值,而方法異常退出時,返回地址是要通過異常處理器來確定的,棧幀中一般不會儲存這部分資訊。

方法退出的過程實際上等同於把當前棧幀出站,因此退出時可能執行的操作有:恢複上層方法的局部變數表和運算元棧,如果有傳回值,則把它壓入調用者棧幀的運算元棧中,調整 PC 計數器的值以指向方法調用指令後面的一條指令。

本地方法棧

該地區與虛擬機器棧所發揮的作用非常相似,只是虛擬機器棧為虛擬機器執行 Java 方法服務,而本地方法棧則為使用到的本地作業系統(Native)方法服務。

Java 堆

Java Heap 是 JAVA 虛擬機器所管理的記憶體中最大的一塊,它是所有線程共用的一塊記憶體地區。幾乎所有的對象執行個體和數組都在這類分配記憶體。Java Heap 是垃圾收集器管理的主要區域,因此很多時候也被稱為“GC堆”。

根據 JAVA 虛擬機器規範的規定,Java 堆可以處在物理上不連續的記憶體空間中,只要邏輯上是連續的即可。如果在堆中沒有記憶體可分配時,並且堆也無法擴充時,將會拋出 OutOfMemoryError 異常。

方法區

方法區也是各個線程共用的記憶體地區,它用於儲存已經被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的代碼等資料。方法地區又被稱為“永久代”,但這僅僅對於 Sun HotSpot 來講,JRockit 和 IBM J9 虛擬機器中並不存在永久代的概念。JAVA 虛擬機器規範把方法區描述為 Java 堆的一個邏輯部分,而且它和 Java Heap 一樣不需要連續的記憶體,可以選擇固定大小或可擴充,另外,虛擬機器規範允許該地區可以選擇不實現記憶體回收。相對而言,垃圾收集行為在這個地區比較少出現。該地區的記憶體回收目標主要針是對廢棄常量的和無用類的回收。運行時常量池是方法區的一部分,Class 檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池(Class檔案常量池),用於存放編譯器產生的各種字面量和符號引用,這部分內容將在類載入後存放到方法區的運行時常量池中。運行時常量池相對於 Class 檔案常量池的另一個重要特徵是具備動態性,Java 語言並不要求常量一定只能在編譯期產生,也就是並非預置入 Class 檔案中的常量池的內容才能進入方法區的運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用比較多的是 String 類的 intern()方法。

根據 JAVA 虛擬機器規範的規定,當方法區無法滿足記憶體配置需求時,將拋出 OutOfMemoryError 異常。

直接記憶體

直接記憶體並不是虛擬機器運行時資料區的一部分,也不是 JAVA 虛擬機器規範中定義的記憶體地區,它直接從作業系統中分配,因此不受 Java 堆大小的限制,但是會受到本機總記憶體的大小及處理器定址空間的限制,因此它也可能導致 OutOfMemoryError 異常出現。在 JDK1.4 中新引入了 NIO 機制,它是一種基於通道與緩衝區的新 I/O 方式,可以直接從作業系統中分配直接記憶體,即在堆外分配記憶體,這樣能在一些情境中提高效能,因為避免了在 Java 堆和 Native 堆中來回複製資料。

記憶體溢出

下面給出個記憶體地區記憶體溢出的簡單測試方法。

這裡有一點要重點說明,在多線程情況下,給每個線程的棧分配的記憶體越大,反而越容易產生記憶體溢出異常。作業系統為每個進程分配的記憶體是有限制的,虛擬機器提供了參數來控制 Java 堆和方法區這兩部分記憶體的最大值,忽略掉程式計數器消耗的記憶體(很小),以及進程本身消耗的記憶體,剩下的記憶體便給了虛擬機器棧和本地方法棧,每個線程分配到的棧容量越大,可以建立的線程數量自然就越少。因此,如果是建立過多的線程導致的記憶體溢出,在不能減少線程數的情況下,就只能通過減少最大堆和每個線程的棧容量來換取更多的線程。

另外,由於 Java 堆內也可能發生記憶體泄露(Memory Leak),這裡簡要說明一下記憶體泄露和記憶體溢出的區別:

記憶體泄露是指分配出去的記憶體沒有被回收回來,由於失去了對該記憶體地區的控制,因而造成了資源的浪費。Java 中一般不會產生記憶體泄露,因為有記憶體回收行程自動回收垃圾,但這也不絕對,當我們 new 了對象,並儲存了其引用,但是後面一直沒用它,而記憶體回收行程又不會去回收它,這邊會造成記憶體泄露,

記憶體溢出是指程式所需要的記憶體超出了系統所能分配的記憶體(包括動態擴充)的上限。

對象執行個體化分析

對記憶體配置情況分析最常見的樣本便是對象執行個體化:

Object obj = new Object();

這段代碼的執行會涉及 Java 棧、Java 堆、方法區三個最重要的記憶體地區。假設該語句出現在方法體中,及時對 JVM 虛擬機器不瞭解的 Java 使用這,應該也知道 obj 會作為參考型別(reference)的資料儲存在 Java 棧的本地變數表中,而會在 Java 堆中儲存該引用的執行個體化對象,但可能並不知道,Java 堆中還必須包含能尋找到此物件類型資料的地址資訊(如物件類型、父類、實現的介面、方法等),這些類型資料則儲存在方法區中。

另外,由於 reference 類型在 JAVA 虛擬機器規範裡面只規定了一個指向對象的引用,並沒有定義這個引用應該通過哪種方式去定位,以及訪問到 Java 堆中的對象的具體位置,因此不同虛擬機器實現的對象訪問方式會有所不同,主流的訪問方式有兩種:使用控制代碼池和直接使用指標。

通過控制代碼池訪問的方式如下:

通過直接指標訪問的方式如下:

這兩種對象的訪問方式各有優勢,使用控制代碼訪問方式的最大好處就是 reference 中存放的是穩定的控制代碼地址,在對象被移動(垃圾收集時移動對象是非常普遍的行為)時只會改變控制代碼中的執行個體資料指標,而 reference 本身不需要修改。使用直接指標訪問方式的最大好處是速度快,它節省了一次指標定位的時間開銷。目前 Java 預設使用的 HotSpot 虛擬機器採用的便是是第二種方式進行對象訪問的。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.