JAVA虛擬機運行時數據區(2).pngjava
一塊較小的內存空間,能夠看做是當前線程所執行的字節碼的行號指示器。java虛擬機多線程是經過線程輪流切換並分配處理器執行時間的方式來實現的。在任何一個肯定的時刻,一個處理器(對於多核來講,就是一個核)都會執行一條線程中的指令。由於,爲了線程切換後能恢復到正確的位置,每條現成都須要一個獨立的程序計數器,各個線程之間計數器互相不影響,獨立存儲,這類內存區域爲「線程私有」內存,若是正在執行的是Native方法,則技術器數值爲空,此區域是java虛擬機中沒有規定任何OutofMemoryError狀況的區域web
線程私有,生命週期與線程相同。虛擬機棧描述的是java方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀(Stack Frame)用於存儲局部變量表,操做數棧,動態鏈接,發放出口等信息。每一個方法從調用直到執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。算法
老師們總說的「堆內存」(Heap)和「棧內存」(Heap),其中的棧指的是上圖中的「虛擬機棧」或「局部變量表」部分。
虛擬機棧是一個大的主體,包括局部變量表、操做數棧,動態鏈接,返回地址等數組
當前線程調用函數內的局部變量。
「變量槽」(slot)是局部變量表的最小單位,通常爲32位大小,一個slot能夠存放boolean,byte,char,short,int,float,reference和returnAddress8種類型,reference表示一個對象的實例引用,經過他能夠得到對象在java堆中存放的起始位置和數據類型等信息,slot經過2個連續的空間存放long跟double類型,returnAddress類型是爲字節碼指令jsr、jsr_w和ret服務的,他指向了一條字節碼指令的地址
變量槽的複用:
虛擬機經過索引定位的方式使用局部變量表。局部變量表存放的是方法參數和局部變量。當調用方法是非static 方法時,局部變量表中第0位索引的 Slot 默認是用於傳遞方法所屬對象實例的引用,即 「this」 關鍵字指向的對象。分配完方法參數後,便會依次分配方法內部定義的局部變量。多線程
public class Test { public static void main(String[] args) { { byte[] temp = new byte[64 * 1024 * 2014]; } System.gc(); } } [GC (System.gc()) 131353K->129368K(245248K), 0.0028307 secs] [Full GC (System.gc()) 129368K->129283K(245248K), 0.0053750 secs]
public class Test { public static void main(String[] args) { { byte[] temp = new byte[64 * 1024 * 2014]; } int a=1; System.gc(); } } [GC (System.gc()) 131353K->129336K(245248K), 0.0049695 secs] [Full GC (System.gc()) 129336K->387K(245248K), 0.0109887 secs]
能夠看到上面兩斷代碼的gc效果「jvm啓動參數:-verbose:gc」,當聲明局部變量temp數組的時候,內存佔用了一部分空間,可是第一段代碼的gc並無回收沒有引用的temp數組。第2段進行的正常的垃圾回收。這裏就是變量槽的複用。主要是根據index來進行。只要被局部變量表中直接或者間接引用的對象都不會被回收.下面再看一個變量槽複用的代碼。jvm
public static void main(String[] args) { { int a = 3; System.out.println(a); } int c = 3; System.gc(); }
經過jclasslib分析。能夠看到下面這圖:函數
和局部變量區同樣,操做數棧也是被組織成一個以字長爲單位的數組。可是和前者不一樣的是,它不是經過索引來訪問,而是經過標準的棧操做—壓棧和出棧—來訪問的。好比,若是某個指令把一個值壓入到操做數棧中,稍後另外一個指令就能夠彈出這個值來使用。ui
虛擬機在操做數棧中存儲數據的方式和在局部變量區中是同樣的:如int、long、float、double、reference和returnType的存儲。對於byte、short以及char類型的值在壓入到操做數棧以前,也會被轉換爲int。this
虛擬機把操做數棧做爲它的工做區——大多數指令都要從這裏彈出數據,執行運算,而後把結果壓回操做數棧。好比,iadd指令就要從操做數棧中彈出兩個整數,執行加法運算,其結果又壓回到操做數棧中,看看下面的示例,它演示了虛擬機是如何把兩個int類型的局部變量相加,再把結果保存到第三個局部變量的spa
begin iload_0 // push the int in local variable 0 onto the stack iload_1 // push the int in local variable 1 onto the stack iadd // pop two ints, add them, push result istore_2 // pop int, store into local variable 2 end
此處爲空,沒有太理解
當方法返回時,可能進行3個操做:
恢復上層方法的局部變量表和操做數棧
把返回值壓入調用者調用者棧幀的操做數棧
調整 PC 計數器的值以指向方法調用指令後面的一條指令
虛擬機規範並無規定具體虛擬機實現包含什麼附加信息,這部分的內容徹底取決於具體實現。在實際開發中,通常會把動態鏈接,方法返回地址和附加信息所有歸爲一類,稱爲棧幀信息。
本地方法棧與虛擬機棧做用類似,區別爲虛擬機棧爲虛擬機執行java方法服務,本地方法棧則爲虛擬機使用到的native方法服務。
java堆是java虛擬機所管理內存中最大的一塊。java堆是全部線程共享的一塊內存區域
,在虛擬機啓動時建立,堆(Heap)的惟一目的就是存放對象實例
,全部的對象實例以及數組都要在堆上分配,同時堆也是垃圾收集器管理的主要區域。從內存回收來看,如今的收集器都採用分代收集算法,因此java堆中還能夠分爲「新生代」和「老年代」,再細緻能夠分爲eden空間,From Survivor空間,To Survivor空間等.根據java虛擬機規範的規定,java堆能夠處於物理上不連續的內存空間中,只要邏輯上是連續的便可。當前主流的虛擬機都是按照可擴展來實現的(經過Xmx和Xms)。若是堆中沒有內存完成實例分配而且堆也沒法擴展的時候,就會出先OOM異常。
堆內存
大小經過-Xms -Xmx來指定大小,堆內存分爲新生代和老年代。經過-Xmx/-Xms來分配最大和最小堆內存。
新生代和老年代默認的空間比例爲1:2。能夠經過-XX:NewRatio來配置,設置-XX:NewRation=3表示年輕代與老年代的比是1:3即年輕代佔年輕代+老年代內存的4分之1.
新生代中細分爲eden和From Survivor和To Survivor三個區。默認eden:FromSurvivor:ToSurvivor=8:1:1。
發生在新生代的是Minor GC,採用的是複製算法。
通常新建立的對象都會分配到eden區(對於一些較大的對象 ( 即須要分配一塊較大的連續內存空間 ) 則是直接進入到老年代。)。當這些對象通過第一次Minor gc後,若是仍然存活的會被移動到survivor區。由於java中建立的對象基本是朝生夕死(80%),第一次Minor gc會回收不少的對象。
MinorGC:從年輕代空間(包括 Eden 和 Survivor 區域)回收內存被稱爲 Minor GC。
當 JVM 沒法爲一個新的對象分配空間時會觸發 Minor GC,好比當 Eden 區滿了。因此分配率越高,越頻繁執行 Minor GC。
內存池被填滿的時候,其中的內容所有會被複制,指針會從0開始跟蹤空閒內存。Eden 和 Survivor 區進行了標記和複製操做,取代了經典的標記、掃描、壓縮、清理操做。因此 Eden 和 Survivor 區不存在內存碎片。寫指針老是停留在所使用內存池的頂部。
執行 Minor GC 操做時,不會影響到永久代。從永久代到年輕代的引用被當成 GC roots,從年輕代到永久代的引用在標記階段被直接忽略掉
全部的 Minor GC 都會觸發「stop-the-world」,中止應用程序的線程。對於大部分應用程序,停頓致使的延遲都是能夠忽略不計的。其中的真相就 是,大部分 Eden 區中的對象都能被認爲是垃圾,永遠也不會被複制到 Survivor 區或者老年代空間。若是正好相反,Eden 區大部分新生對象不符合 GC 條件,Minor GC 執行時暫停的時間將會長不少。
survivor分爲From Survivor和To Survivor兩個區。
這兩個區總有一個是空的。在GC開始的時候,在eden中回收一些瞬時對象,剩下的存活對象會複製到To區域中(對象年齡+1)。而在From區中,存活的對象根據年齡來決定去向,年齡到達必定值的時候,對象會被移動到老年代中,沒有達到必定值的時候,對象會移動到To區,而且年齡+1.通過這次GC。eden區和From區已經被清空。下次GC的時候,會把對象從To移動到From區中,就會變成eden和To區清空。每次GC進行重複上面的複製回收動做。使From或To區總有一個是清空的。當To 或 From區一個被填滿以後,會將全部對象移動到老年代中。
-XX:SurvivorRatio:設置eden和survivor的比值。-XX:SurvivorRatio=3表示eden:Survivor=3:2;eden佔5分之三,From/To Survivor各佔五分之一
-XX:+PrintTenuringDistribution:顯示每次Minor GC時Survivor區中各個年齡段的對象的大小
-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold:晉升到老年代的對象年齡的最小值和最大值。每一個對象在堅持過一次Minor GC以後,年齡就加0
老年代發生的GC稱爲MajorGC,採用的是標記-清除算法。
標記-清除算法收集垃圾的時候會產生許多的內存碎片 ( 即不連續的內存空間 ),此後須要爲較大的對象分配內存空間時,若沒法找到足夠的連續的內存空間,就會提早觸發一次 GC 的收集動做
-XX:+PrintGCDetails:控制檯顯示 GC 相關的日誌信息
Full GC == Major GC指的是對老年代/永久代的stop the world的GC
FullGC與MajorGC的區別{:target="_blank"}
方法區與java堆同樣,是各個線程共享的內存區域,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。它還有一個別名叫Non-Heap(非堆)。對於HotSpot虛擬機。他還有一個別名,叫作永久代。在java8以前,能夠經過-XX:MaxPermSize
來設置方法區的上限。
在java8以前,若是沒法分配內存會拋出OOM異常。能夠經過上面的命令來增長上限值
在java8中,HotSpot已經移除了這個方法區。有了一個新的「元空間」來替代方法區。
它是本地堆內存
中的一部分
它能夠經過-XX:MetaspaceSize和-XX:MaxMetaspaceSize來進行調整
當到達XX:MetaspaceSize所指定的閾值後會開始進行清理該區域
若是本地空間的內存用盡了會收到java.lang.OutOfMemoryError: 「Metadata space」或「Java heap space」的錯誤信息。
和持久代相關的JVM參數-XX:PermSize及-XX:MaxPermSize將會被忽略掉,而且在啓動的時候給出警告信息。
充分利用了Java語言規範中的好處:類及相關的元數據的生命週期與類加載器的一致
絕大多數的類元數據的空間都從本地內存中分配
用來描述類元數據的類也被刪除了,分元數據分配了多個虛擬內存空間
給每一個類加載器分配一個內存塊的列表,只進行線性分配。塊的大小取決於類加載器的類型, sun/反射/代理對應的類加載器的塊會小一些。
不會單獨回收某個類,若是GC發現某個類加載器再也不存活了,會把相關的空間整個回收掉。這樣減小了碎片,並節省GC掃描和壓縮的時間。
使用-XX:MaxMetaspaceSize參數能夠設置元空間的最大值,默認是沒有上限的,也就是說你的系統內存上限是多少它就是多少。
使用-XX:MetaspaceSize選項指定的是元空間的初始大小,若是沒有指定的話,元空間會根據應用程序運行時的須要動態地調整大小。
一旦類元數據的使用量達到了「MaxMetaspaceSize」指定的值,對於無用的類和類加載器,垃圾收集此時會觸發。爲了控制這種垃圾收集的頻率和延遲,合適的監控和調整Metaspace很是有必要。過於頻繁的Metaspace垃圾收集是類和類加載器發生內存泄露的徵兆,同時也說明你的應用程序內存大小不合適,須要調整。