學習筆記 | 深刻理解Java內存模型

Java內存模型

Java 虛擬機在執行 Java 程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域。這些區域都有各自的用途,以及建立和銷燬的時間,有的區域隨着虛擬機進程啓動而存在,有些區域則是依賴用戶線程的啓動和結束而創建和銷燬。 Java 虛擬機所管理的內存包括如下幾個運行時數據區域:算法

程序計數器

程序計數器(Program Counter Register)是一塊很是小的內存空間,它的做用能夠看做是當前線程所執行的字節碼的行號指示器。每一個線程都有一個獨立的程序計數器,所以程序計數器是線程私有的一塊空間。此外,程序計數器是 Java 虛擬機規定的惟一不會發生內存溢出的區域數組

在虛擬機的概念模型裏,字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等都須要依賴這個計數器來完成。性能優化

方法區

方法區(Method Area)是一個線程共享的區域,主要用於存儲虛擬機加載的類信息常量(final)、靜態變量(static),JIT(即時編譯器)編譯後的代碼等數據。性能

在 JDK1.7 及其以前,方法區是堆的一個「邏輯部分」(一片連續的堆空間),但爲了與堆作區分,方法區還有一個名字叫「非堆」,也有人用「永久代」(HotSpot 對方法區的實現方法)來表示方法區。優化

當方法區沒法知足內存分配需求時,將拋出OutOfMemoryError異常。spa

從 JDK1.8 開始,已經不存在永久代,替代它的一塊空間叫作元空間線程

去永久代的緣由:3d

  1. 字符串存在永久代中,容易出現性能問題和內存溢出;指針

  2. 類及方法的信息等比較難肯定其大小,所以對於永久代的大小指定比較困難,過小容易出現永久代溢出,太大則容易致使老年代溢出;code

  3. 永久代會爲 GC 帶來沒必要要的複雜度,而且回收效率偏低。

運行時常量池

Class 文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池(Constant Pool Table),用於存儲編譯期生成的各類字面量和符號引用,這部份內容將在類加載後存放到方法區的運行時常量池中。

當常量池沒法再申請到內存時會拋出OutOfMemoryError異常。

虛擬機棧

虛擬機棧(Java Virtual Machine Stacks)也是每一個線程私有的一塊內存空間,它的生命週期與線程相同。虛擬機棧描述的是 Java 方法執行的內存模型:每一個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每個方法被調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。

局部變量表存放了編譯期可知的各類基本數據類型(boolean、byte、char、short、int、float、long、double)、對象應用(reference 類型,它不等同於對象自己,根據不一樣的虛擬機實現,它多是一個執行對象起始地址的引用指針,也可能指向一個表明對象的句柄或者其餘與此對象相關的位置)和 returnAddress 類型(指向了一條字節碼指令的地址)。

若是線程請求的棧深度大於虛擬機所容許的深度,將拋出 StackOverflowError 異常;若是虛擬機棧能夠動態擴展,當擴展時沒法申請到足夠的內存時會拋出 OutOfMemoryError 異常。

本地方法棧

本地方法棧與虛擬機棧的區別是,虛擬機棧執行的是 Java 方法,本地方法棧執行的是本地方法(Native Method),其餘基本上一致,在 HotSpot 中直接把本地方法棧和虛擬機棧合二爲一。本地方法棧區域會拋出StackOverflowErrorOutOfMemoryError異常。

堆內存

堆內存主要用於存放對象和數組,它是 JVM 的內存中最大的一塊區域,堆內存和方法區都被全部線程共享,在虛擬機啓動的時候建立。在垃圾收集的層面來看,因爲如今收集器基本都採用分代收集算法,堆內存空間還分爲新生代(Young Generation)和老年代(Old Generation),新生代分爲 Eden 區、From Survivor 區、To Survivor 區。

新生代

是用來存放新生的對象。通常佔據堆的 1/3 空間。因爲頻繁建立對象,因此新生代會頻繁觸發 MinorGC 進行垃圾回收。新生代又分爲 Eden 、From Survivor 、To Survivor 三個區。

  • Eden 區:Java 新對象的出生地(若是新建立的對象佔用內存很大,則直接分配到老年代)。當 Eden 區內存不夠的時候就會觸發 MinorGC,對新生代區進行一次垃圾回收。

  • From Survivor:上一次 GC 的倖存者,做爲這一次 GC 的被掃描者

  • To Survivor:保留了一次 MinorGC 過程當中的倖存者。

JVM 每次只會使用 Eden 和其中一塊 Survivor 區域來爲對象服務,因此不管何時,老是有一塊 Survivor 區域是空閒的。新生代實際可用的內存空間爲 9/10(即 90%)的新生代空間。

老年代

主要存放應用程序中生命週期長的內存對象。老年代的對象比較穩定,因此 MajorGC 不會頻繁執行。在進行 MajorGC 前通常都先進行了一次 MinorGC,使得有新生代的對象進入老年代,致使空間不夠用才觸發。當沒法找到足夠大的連續空間分配給新建立的較大對象時也會提早觸發一次 MajorGC 進行垃圾回收以騰出空間。

MajorGC 採用標記清除算法:首先掃描一次全部老年代,標記出存活的對象,而後回收沒有標記的對象。MajorGC 的耗時比較長,由於要掃描再回收。 MajorGC 會產生內存碎片,爲了減小內存損耗,通常須要進行合併或者標記出來方便下次直接分配。當老年代也滿了裝不下的時候,就會拋出 OOM (Out Of Memory)異常。

元空間

從 JDK1.8 開始,已經不存在永久代,替代它的一塊空間叫作「元空間」,和永久代相似,都是 JVM 非法對方法區的實現,可是元空間並不在虛擬機中,而是使用本地內存,元空間的大小僅受本地內存限制。元空間的大小能夠經過 -XX:MetaspaceSize-XX:MaxMetaspaceSize來指定。

關注得到更多分享


好文推薦:

相關文章
相關標籤/搜索