1. 程式人生 > >Java 記憶體分配策略

Java 記憶體分配策略

參考來源於深入理解Android虛擬機器一書。

1. Java 虛擬機器棧 VM Stack

棧中的資料是以棧幀(Stack Frame)的格式存在的,虛擬機器在執行每一個方法的呼叫時都會建立一個棧幀的資料結構,棧幀包括了方法的區域性變量表(輸入引數、輸出引數、方法內的變數)、棧操作(記錄出棧、入棧的操作)、動態連結、方法、類檔案等一些額外的附加資訊。

區域性變量表中存放了編譯期的基本資料型別(boolean、byte、int、char、short、float、long、double)和物件的引用(reference型別,並不是物件本身)。每一個方法的呼叫,其實就是對應著一個棧幀在虛擬機器裡入棧出棧的過程。對於活動執行緒中棧頂的棧幀,稱為當前棧,這個棧幀所關聯的方法稱為當前方法。

虛擬機器棧是執行緒私有的,是線上程建立時建立的,它的生命週期跟隨執行緒的生命週期,執行緒結束時釋放記憶體,是不需要垃圾回收的。當方法A被呼叫就會產生一個棧幀F1,並壓入到棧中,A方法又呼叫了B方法,產生棧幀F2也被壓入棧,方法執行完畢,先彈出F2,再彈出F1,遵循”先進後出”原則,執行緒結束,棧釋放。

這裡寫圖片描述

Java棧會丟擲的異常:

  • 如果執行緒請求的棧深度大於虛擬機器所允許的深度,會丟擲StackOverflowError異常。
  • 如果無法申請到足夠的記憶體來實現棧的對臺擴充套件,或者沒有足夠的記憶體為一個新執行緒建立Java棧,會丟擲OutOfMemoryError異常。

2. Java 堆 Java Heap

Java堆用來存放由關鍵字new建立的物件和陣列。在堆中分配記憶體,是所有執行緒共享的記憶體區域。堆在虛擬機器啟動時建立。Java堆記憶體區域的唯一目的就是存放物件的例項,幾乎所有的物件例項都在這裡分配記憶體。

實際上,棧中的變數指向堆記憶體中的變數,這就是Java的指標。

Java堆也是垃圾回收機制管理的主要區域,因此很多時候也會內成為”GC堆”(Garbage Collected Heap)
一個Java虛擬機器例項只會存在一個堆記憶體,堆記憶體分成三部分:

  • 永久儲存區 Permanent Space :存放JDK自身攜帶的Class Interface的元資料,在此區域的資料是不會被GC的,只有關閉虛擬機器才會被釋放所佔用的記憶體。
  • 新生區 Young Generation Space :新生區是類誕生、成長、消亡的區域,一個類在這裡產生、應用,最後被GC回收。新生區又分為2部分:伊甸區(Eden Space)和倖存區(Survivor Space)。所有類都在伊甸區被new出來。倖存區又分為:倖存0區(Survivor 0 Space)和倖存1區(Survivor 1 Space)。當伊甸區記憶體用完時,程式又需要建立物件,Java虛擬機器的垃圾回收器就會對伊甸區進行GC,將此區不再被其他物件所引用的物件進行銷燬,剩餘的物件移動到倖存0區。如果倖存0區也滿了,在對該區進行GC,然後移到倖存1區。如果倖存1區也滿了,就移動到養老區。
  • 養老區 Tenure Generation Space :養老區用於儲存從新生區帥選出來的Java物件。

這裡寫圖片描述

如果堆中沒有可用記憶體完成類例項或陣列的分配,在物件數量達到最大的堆容量限制後就會丟擲OutOfMemoryError異常。

通過-Xmx和-Xms限制堆記憶體大小。

3. 本地方法棧 Native Method Stack

在Java中,本地方法棧中執行的不是Java語言所編寫的程式碼,如C、C++。

4. 方法區 Method Area

在Java系統中,方法區在虛擬機器啟動時建立,是所有執行緒共享的記憶體區域,用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼資料等。有個別名叫做Non-Heap(非堆)。

方法區的回收目標主要針對的是常量池的回收和對類的解除安裝。方法區的大小可以控制。

在方法區無法滿足記憶體分配需求時,將會丟擲OutOfMemoryError異常,在異常後面跟隨的資訊提示是”PermGen Space”,說明執行時常量池屬於方法區(HotSpot虛擬機器永久代)的一部分。

通過-XX:PermSize和-XX:MaxPermSize限制方法區大小。

5. 執行時常量池 Runtime Constant Pool

執行時常量池是方法區的一部分。在Class檔案中,除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊就是常量池(Constant Pool Table),用於存放編譯期間生成的各種字面量和符號引用,這部分內容將在類載入後,存放到方法區的執行時常量池中。

類的常量池在該類的Java Class檔案被載入Java 虛擬機器成功地載入時建立。

執行時常量池和Class檔案的常量池的區別:

  • Class檔案的常量池在編譯期生成,在執行期被裝載。
  • 執行時常量池具備動態性,在執行期間也可以將新的常量放入執行時常量池中。Native方法String.intern()就可以向執行時常量池中新增內容,該方法的作用是:如果池中已經包含了等於此String物件的字串,則返回池中代表這個字串的String物件;否則,將此String物件的字串新增到常量池中,並且返回此String物件的引用。

執行時常量池受到方法區記憶體的限制,當執行時常量池無法在申請到記憶體時,就會丟擲OutOfMemoryError異常。

在裝載Class檔案時,如果Class檔案的常量池的建立需要比方法區中需要更多的記憶體時,也會丟擲OutOfMemoryError異常。

6. 直接記憶體 Direct Memory

直接記憶體並不是虛擬機器執行時資料區的一部分,從JDK1.4開始,加入NIO(new Input/Output),可以通過Native函式庫直接分配堆外記憶體,然後通過一個儲存在Java堆裡面的DirectByteBuffer物件作為這塊記憶體的引用進行操作。

本機直接記憶體的分配不會受到Java堆大小的限制,但是會受到本機總記憶體(RAM、SWAP區或者分頁檔案)的大小及處理器定址空間的限制。如果超出了實體記憶體的限制,會丟擲OutOfMemoryError異常。

通過-XX:MaxDirectMemorySize限制直接記憶體大小。

7. 物件訪問

Object obj = new Object();

如果這段程式碼出現在方法中,那”Object obj”就會反映到Java棧中區域性變量表中,作為一個reference型別資料出現。而”new Object()”就會反映到Java堆中,形成一塊儲存了Object型別所有資料值的結構化記憶體。另外,Java堆中還必須包含能查詢到此物件型別資料的地址資訊,這些型別資料儲存在方法區中。

由於reference型別在Java虛擬機器只規定了一個指向物件的引用,並沒有規定通過哪種方式去查詢Java堆中物件的具體位置。不同的虛擬機器實現的方式不同,主流又2種:使用控制代碼和直接指標。

  • 如果使用控制代碼訪問方式,Java堆中將會劃分出一塊記憶體來作為控制代碼池,reference中儲存的就是物件的控制代碼地址,而控制代碼中包含了物件例項資料的地址資訊和物件型別資料的地址資訊。

這裡寫圖片描述

  • 如果使用直接指標訪問方式,reference中直接儲存的就是物件地址。(Sun HotSpot使用的是直接指標的訪問方式)。使用直接指標訪問的好處是速度快,節省了一次指標定位的時間開銷。

這裡寫圖片描述

8. 記憶體洩漏 Memory Leak

一般我們常說的記憶體洩漏是指堆記憶體的洩露。堆記憶體是指程式從堆中分配的,大小任意的(記憶體塊的大小可以在程式執行期決定),使用完後必須顯示釋放的記憶體。

記憶體洩漏有四種:

  • 常發性記憶體洩漏:洩露程式碼唄執行多次,很頻繁的發生。
  • 偶發性記憶體洩漏:特定環境下的才會發生的記憶體洩漏。
  • 一次性記憶體洩漏:記憶體洩漏的程式碼只會背執行一次,如Singleton類的洩露。
  • 隱式記憶體洩漏:程式在執行過程中分配的記憶體,直到程式結束時才釋放記憶體。嚴格的說,並沒有發生記憶體洩露,但是因為不及時的釋放記憶體,可能最終會導致系統記憶體耗盡。

相關推薦

讀薄《深入理解 JAVA 虛擬機器》Java記憶體分配策略

記憶體分配規則不是固定的,取決於當前使用的是哪一種垃圾收集器以及虛擬機器配置。 物件優先在 Eden 上分配 大多數情況下,物件分配在 Eden 上,當記憶體不足的時候觸發一次 Minor GC。 大物件分配進老年代 需要連續記憶體空間的物件,最典型的是很長的字串已經陣列,寫程式的時候應該避免生命週期

Java記憶體分配策略與垃圾收集器

判斷物件是否死亡的方法 1)引用計數演算法 給物件新增一個引用計數器,每當一個地方引用它時,計數器加1,當引用失效,計數器減1,任何時刻計數器為0的物件就是不可能再被使用。然而主流的Java虛擬機器裡面沒有選用引用計數演算法來管理記憶體,因為無法解決物件之間相互

Java 記憶體分配策略

參考來源於深入理解Android虛擬機器一書。 1. Java 虛擬機器棧 VM Stack 棧中的資料是以棧幀(Stack Frame)的格式存在的,虛擬機器在執行每一個方法的呼叫時都會建立一個棧幀的資料結構,棧幀包括了方法的區域性變量表(輸入引

JVM筆記4:Java記憶體分配策略及配置引數

簡單來說,物件記憶體分配主要是在堆中分配。但是分配的規則並不是固定的,取決於使用的收集器組合以及JVM記憶體相關引數的設定 以下介紹幾條基本規則(使用的ParNew+Serial Old收集器組合): 一,物件優先在新生代Eden區分配 //-XX:+UseParNew

Java記憶體分配策略Java執行時記憶體分配

Java 記憶體分配策略 Java 程式執行時的記憶體分配策略有三種,分別是靜態分配,棧式分配,和堆式分配,對應的,三種儲存策略使用的記憶體空間主要分別是靜態儲存區(也稱方法區)、棧區和堆區。 靜態儲存區(方法區):主要存放靜態資料、全域性 static 資

淺談Java記憶體分配策略

Java 記憶體分配策略 Java 程式執行時的記憶體分配策略有三種,分別是靜態分配,棧式分配,和堆式分配,對應的,三種儲存策略使用的記憶體空間主要分別是靜態儲存區(也稱方法區)、棧區和堆區。 靜態儲存區(方法區):主要存放靜態資料、全域性 static 資料和常量。這

深入理解Java虛擬機器總結一垃圾收集器與記憶體分配策略(二)

深入理解Java虛擬機器總結一垃圾收集器與記憶體分配策略(二) 垃圾回收概述 如何判定物件為垃圾物件 垃圾回收演算法 垃圾收集器詳解 記憶體分配策略 垃圾回收概述 如何判定物件為垃圾物件 引用計數法: 在物件

Java虛擬機器:記憶體分配策略

  Java中提倡的自動記憶體管理機制最終可以歸結為自動化的解決兩個問題:給物件分配記憶體和回收分配給物件的記憶體。在之前的部落格中已經詳細講解了記憶體回收體系及原理,下面我們來探討給物件分配記憶體那些事兒。        物件的記憶體分配,總體

Java底層的記憶體分配策略

Java記憶體分配的主要區域: 1、棧:存放基本型別的資料和物件的引用,物件本身是存放在堆中 2、堆:存放用new產生的物件資料 3、常量池:存放常量 通常常量池有兩種形態: 執行時常量池:則是jvm虛擬機器在完成類裝載操作後,將*.class檔案中的常量池載入到記憶體中,並儲存

Java虛擬機器】垃圾收集器和記憶體分配策略

垃圾收集器和記憶體分配策略 垃圾收集器 Serial 收集器 ParNew 收集器 Parallel Scavenge 收集器 Serial Old 收集器 Parallel Old 收集器 CMS 收集器(C

深入Java虛擬機器閱讀感(二)-Java垃圾回收器與記憶體分配策略

垃圾回收器主要演算法:       1、引用計數法。給物件新增一個計數器,當物件被使用時則加1,當引用失效時則減1,當計數為0時則認為該物件可以被回收。由於該算演算法無法解決物件相互引用而計數不會減為0,導致該物件無法回收,所以該演算法不是Java虛擬垃圾回收器

《深入理解JAVA虛擬機器》詳細解讀(第三章 ):垃圾收集器與記憶體分配策略

  目錄 一、垃圾收集器與記憶體分配策略 1 概述 2 物件已經死亡? 2.1引用計數法(未使用) 2.2可達性分析演算法 2.3 再談引用 2.4 生存還是死亡 2.5 回收方法區 3 垃圾收集演算法 3.1 複製演算法(Copy) 3

Java虛擬機器筆記-1(Java技術體系&自動記憶體管理機制&記憶體區域與記憶體溢位&垃圾收集器與記憶體分配策略

世界上沒有完美的程式,但寫程式是不斷追求完美的過程。 Devices(裝置、裝置)、GlassFish(商業相容應用伺服器) 目錄 1. Java技術體系包括: Java技術體系的4個平臺 虛擬機器分類 HotSpot VM 模組化、混合程式設計 多核並行

JAVA垃圾收集器與記憶體分配策略

3.1 概述 LISP是第一門使用記憶體動態分配和垃圾收集技術的語言。 CG需要完成的三件事: 1、哪些記憶體需要回收? 2、什麼時候回收? 3、如何回收? JAVA堆和方法區中,一個介面中的多個實現類需要的記憶體可能不一樣,一個方法中的多個分支需要的記憶體也不一

Java虛擬機器6:記憶體分配策略

大物件直接進入老年代Java的自動記憶體管理最終可以歸結為自動化地解決了兩個問題: 給物件分配記憶體 回收分配給物件的記憶體      物件的記憶體分配通常是在堆上分配(除此以外還有可能經過JIT編譯後被拆散為標量型別並間接地棧上分配),物件主要分配在新生代的Eden區上

讀書筆記《深入理解Java虛擬機器》 (三)物件已死?與記憶體分配策略

物件是否可回收 引用計數演算法 給物件新增一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時就減1;當等於0時就認為物件不可能再被使用。問題:當兩個物件相互引用時,就無法回收了。 可達性分析演算法 通過一系列的稱為“GC Roots”的物件作為起

深入理解Java虛擬機器——垃圾收集器與記憶體分配策略(讀書筆記)

判斷物件是否存活 1、引用計數法 給物件新增一個引用計數器,每當有一個地方引用它時,計數器值加1,當引用失效時,計數器值減1, 任何時刻計數器為0的物件就是不可能再被使用的。 缺點:不能解決物件之間迴圈引用的問題 2、根搜尋演算法(GC Roots Tracing)

深入理解Java虛擬機器讀書筆記2----垃圾收集器與記憶體分配策略

二 垃圾收集器與記憶體分配策略 1 JVM中哪些記憶體需要回收?     JVM垃圾回收主要關注的是Java堆和方法區這兩個區域;而程式計數器、虛擬機器棧、本地方法棧這3個區域隨執行緒而生,隨執行緒而滅,隨著方法結束或者執行緒結束記憶體自然

JVM垃圾收集器與記憶體分配策略(總結自《深入理解Java虛擬機器》)

1、物件可用性判斷 垃圾收集器在回收物件前,需要判斷哪些物件沒有被廢棄,哪些物件已經廢棄了(即無法通過任何途徑使用的物件)。所以,垃圾收集器需要一種演算法來判定這個物件是否需要回收。 (1)引用計數演算法 引用計數演算法的基本思想是給一個物件新增一個引用計數器,

《深入理解Java虛擬機器》學習筆記之垃圾收集器與記憶體分配策略

一、概述 GC(Garbage Collection)需要完成的三件事 (1)哪些記憶體需要回收 (2)什麼時候回收 (3)如何回收 GC主要面向Java堆和方法區中的記憶體 原因:這部份