最詳細的Java內存區域劃分及總結

JVM運行時區域劃分:

JVM把它的內存劃分:程序計數器,虛擬機棧,本地方法棧,方法區,JAVA堆。
在這裏插入圖片描述

程序計數器

程序計數器的功能類似於計算機組成原理中的PC寄存器,用於存放下一條指令所在單元的地址。當執行一條指令時,首先需要根據PC中存放的指令地址,將指令由內存取到指令寄存器中,此過程稱爲“取指令”。與此同時,PC中的地址或自動加1或由轉移指針給出下一條指令的地址。此後經過分析指令,執行指令。完成第一條指令的執行,而後根據PC取出第二條指令的地址,如此循環,執行每一條指令。雖然JVM的程序計數器跟PC有所區別,但是在概念上是等同的,JVM中的PC存放的是程序正在執行的字節碼的行號,字節碼解釋器的工作就是通過改變程序計數器的值來選擇下一條需要執行的字節碼指令。
  在JVM中,多線程是通過線程輪流切換來獲得CPU執行時間的,因此,在任一具體時刻,一個CPU的內核只會執行一條線程中的指令,因此,爲了能夠使得每個線程都在線程切換後能夠恢復到切換之前的程序執行位置,每個線程都需要有自己獨立的程序計數器,並且不能互相被幹擾,否則就會影響到程序的正常執行次序。因此,可以這麼說,程序計數器是每個線程所私有的。
  如果線程執行的是一個Java方法,那麼寄存器裏面記錄的就是正在執行的虛擬機字節碼指令的地址,如果線程執行的是一個native方法,那麼寄存器記錄的值爲undefined。
  由於程序計數器是固定寬度的存儲空間,因此,對於程序計數器是不會發生內存溢出現象(OutOfMemory)的
  總結:1,存儲每個線程程序的執行位置,保證多線程執行的有序性。2,線程私有。3,不會發生內存溢出

虛擬機棧

虛擬機棧中存放每個方法執行時創建的棧幀,對於執行引擎來講,活動線程中,只有棧頂的棧幀是有效的,稱爲當前棧幀,這個棧幀所關聯的方法稱爲當前方法執行引擎所運行的所有字節碼指令都只針對當前棧幀進行操作棧幀用於存放局部變量表、操作數棧、動態鏈接、方法返回地址和一些額外的附加信息。在編譯程序代碼時,棧幀中需要多大的局部變量表、多深的操作數棧都已經完全確定了,並且寫入了方法表的 Code 屬性之中。因此,一個棧幀需要分配多少內存,不會受到程序運行期變量數據的影響,而僅僅取決於具體的虛擬機實現
  局部變量表存放了各種編譯器已知的各種基本數據類型,對象引用等;程序員關注的棧內存一般是指的局部變量表的內存,局部變量表所需的內存空間在編譯時期完成分配,在方法運行期間不會改變局部變量表的大小。
  操作數棧:程序中的計算是通過操作數棧來完成的,操作數棧的最大深度也是在編譯的時候就確定了。Java 虛擬機的解釋執行引擎稱爲“基於棧的執行引擎”,其中所指的“棧”就是操作數棧。因此我們也稱 Java 虛擬機是基於棧的,這點不同於 Android 虛擬機,Android 虛擬機是基於寄存器的。基於棧的優點是可移植性強,缺點是速度相對較慢。
  動態鏈接:每個棧幀都包含一個指向運行時常量池的引用,持有這個引用是爲了支持方法調用過程中的動態連接。Class 文件的常量池中存在有大量的符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用爲參數。這些符號引用,一部分會在類加載階段或第一次使用的時候轉化爲直接引用(如 final、static 域等),稱爲靜態解析,另一部分將在每一次的運行期間轉化爲直接引用,這部分稱爲動態連接。
  方法返回地址:方法正常退出時,調用者的 PC 計數器的值就可以作爲返回地址,棧幀中很可能保存了這個計數器值,而方法異常退出時,返回地址是要通過異常處理器來確定的,棧幀中一般不會保存這部分信息。方法退出的過程實際上等同於把當前棧幀出棧,因此退出時可能執行的操作有:恢復上層方法的局部變量表和操作數棧,如果有返回值,則把它壓入調用者棧幀的操作數棧中,調整 PC 計數器的值以指向方法調用指令後面的一條指令。
  -Xss128k:設置每個線程的棧大小,jdk1.5以後每個線程的棧大小爲1M,減小這個值能生成更多的線程,但同時可能會帶來OutOfMemoryError。
  在Java虛擬機規範中針對這個區域規定了兩種異常:1)如果線程請求的棧深度大於虛擬機所允許的深度,將拋出StackOverflowError異常,2)如果虛擬機在動態擴展棧時無法申請到足夠的內存空間,則拋出OutOfMemoryError異常。
  總結:1,虛擬機棧存放每個方法執行時創建的棧幀。2,一個棧幀分配的內存取決於虛擬機的實現。3局部變量表存放的事基本數據類型,對象引用等,並且內存空間在編譯時期已經分配,運行期間並不會改變。4,操作數棧用來完成計算,最大深度編譯期決定
  5,會出現StackOverflowError異常。6,會出現內存溢出(OutOfMemory)異常

本地方法棧

該區域與虛擬機棧所發揮的作用非常相似,只是虛擬機棧爲虛擬機執行 Java 方法服務,而本地方法棧則爲使用到的本地操作系統(Native)方法服務。在JVM規範中,並沒有對本地方法的具體實現方法以及數據結構作強制規定,虛擬機可以自由實現它。在HotSopt虛擬機中直接就把本地方法棧和Java棧合二爲一。
  總結:本地方法棧爲本地操作系統的方法服務。

方法區

在方法區中,存儲了每個類的信息(包括類的名稱、方法信息、字段信息)、靜態變量、常量以及編譯器編譯後的代碼等。
  在方法區中有一個非常重要的部分就是運行時常量池常量池指的是在編譯期被確定,並被保存在已編譯的.class文件中的一些數據。除了包含代碼中所定義的各種基本類型(如:int、long等)和對象型(如String及數組)的常量值(final)還包含一些以文本形式出現的符號引用(#類和接口的全限定名#字段的名稱的描述符#方法和名稱的描述符).虛擬機必須爲每個被裝載的類型維護一個常量池。常量池就是該類型所用到常量的一個有序集合,包括直接常量(string,integer和floating常量)和其他類型字段和方法的符號引用
  方法區和持久代的關係如下:
  “持久代”僅僅是HotSpot存在的一個概念,並且將其置於方法區,JRocket與IBM的VM都不存在這個“持久代”,最新的HotSpot也計劃將其移除。在已經發布的Oracle JDK7 RC(JDK7 build 147)裏,HotSpot VM仍然有PermGen,但許多原本存儲在PermGen裏的東西已經挪到了別的地方。 方法區物理上存在於堆裏,而且是在堆的持久代裏面;但在邏輯上,方法區和堆是獨立的
  總結:1,方法區存儲了每個類的信息。2,運行時常量池包含在方法區。3,常量池就是類所用到的常量的一個有序集合。4,方法區存在於堆裏,而且在堆的持久代裏面,但邏輯上兩者是區分開的。
  
堆區

堆內存由年輕代和老年代組成,其中年輕代又分爲一個Eden區和兩個Survivor區(使用複製收集算法);所有新創建的Object 一般都會存儲在新生代中,如果新生代數據在一次或多次GC後存活下來,那麼將被轉移到Old Generation中
  新建的對象也有可能在老年代上直接分配內存,這主要有兩種情況:一種爲大對象,可以通過啓動參數設置-XX:PretenureSizeThreshold=1024,表示超過多大時就不在年輕代分配,而是直接在老年代分配,此參數在年輕代採用Parallel Scavenge GC時無效,因爲其會根據運行情況自己決定什麼對象直接在老年代上分配內存;另一種爲大的數組對象,且數組對象中無引用外部對象。
  當老年代滿了就需要對老年代進行回收,老年代的垃圾回收稱爲Full GC
  對象訪問會涉及到Java棧、Java堆、方法區這三個內存區域,Object obj = new Object(); Object obj 這部分將會反映到Java棧的本地變量表中,作爲一個reference類型數據出現。而“new Object()”這部分將會反映到Java堆中,形成一塊存儲Object類型所有實例數據值的結構化內存,根據具體類型以及虛擬機實現的對象內存佈局的不同,這塊內存的長度是不固定。另外,在java堆中還必須包括能查找到此對象類型數據(如對象類型、父類、實現的接口、方法等)的地址信息,這些數據類型存儲在方法區中。根據reference去訪問實例對象有兩種訪問方式:句柄訪問方式、指針訪問方式
  內存泄露:指程序中一些對象不會被GC所回收,它始終佔用內存,即被分配的對象引用鏈可達但已無用。(可用內存減少)。內存溢出:程序運行過程中無法申請到足夠的內存而導致的一種錯誤。內存溢出通常發生於OLD段或Perm段垃圾回收後,仍然無內存空間容納新的Java對象的情況。內存泄露是內存溢出的一種誘因,不是唯一因素
  -Xms參數和-Xmx參數可以控制堆內存**,默認空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制**;空餘堆內存大於70%時,JVM會減少堆直到 -Xms的最小限制。因此服務器一般設置-Xms、-Xmx相等以避免在每次GC 後調整堆的大小**。
   JVM使用-XX:PermSize設置非堆內存初始值,默認是物理內存的1/64;由XX:MaxPermSize設置最大非堆內存的大小,默認是物理內存的1/4。
  在GC算法進行垃圾回收時首先要進行對象存活性判斷,一般有兩種方法:引用計數、可達性分析(在java語言中可作爲GC Roots進行可達性分析的對象包括:虛擬機棧中引用的對象,方法區中類靜態屬性實體引用的對象,方法區中常量引用的對象,本地方法棧中JNI引用的對象。)。垃圾收集算法分:標記清除算法、複製算法、標記整理算法JVM的收集器採用分代收集,分代收集是在不同的區使用上面三種算法,以及是否可並行收集。
總結:1,堆,老年代和年輕代組成。2,新生代多次GC後會轉移到老年代。3,新建對象在新生代,兩種情況直接在老年代分配內存,大對象和大的數組對象。4,老年代滿了就進行垃圾回收稱爲Full GC。5,內存泄露:指程序中一些對象不被GC回收,始終佔用內存。內存溢出:程序運行過程中無法申請到足夠內存導致的錯誤。內存泄露是內存溢出的一種誘因,不是唯一因素。6,服務器一般設置-Xms、-Xmx相等以避免在每次GC 後調整堆的大小。7,對象存活性判斷方法:引用計數,可達性分析。8,垃圾收集算法:標記清楚法,複製算法,標記整理法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章