java虛擬機器總結,java虛擬機器
jvm記憶體模型
u 程式計數器
u Java棧(虛擬機器棧)
u 本地方法棧
u Java堆
u 方法區及其運行時常量池
記憶體回收機制
u 新生代和老年代
u 參數設定
u 記憶體回收(Minor GC 和 Full GC)和回收演算法
u finalize()、減少GC開銷、觸發主GC的條件
u 判斷無用對象、四種引用方式、為什麼進行記憶體回收
u String、StringBuffer與StringBuilder
u 類載入機制
類載入機制一 : jvm記憶體模型
第一、程式計數器(PC)
程式計數器(Program Counter Register)是一塊較小的記憶體空間,它可以看做當前線程所執行的位元組碼的行號指標,位元組碼解譯器工作時就是通過改變這個計數器的值來取下一條需要執行的位元組碼指令
由於Java 虛擬機器的多線程是通過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對於多核處理器來說是一個核心)只會執行一條線程中的指令。因此,為了線程切換後能恢複到正確的執行位置,每條線程都需要有一個獨立的程式計數器,各條線程之間的計數器互不影響,隔離儲存區 (Isolated Storage),我們稱這類記憶體地區為“線程私人”的記憶體。
如果線程正在執行的是一個Java 方法,這個計數器記錄的是正在執行的虛擬機器位元組碼指令的地址;如果正在執行的是Natvie 方法,這個計數器值則為空白(Undefined)。
註:程式計數器是線程私人的,每條線程都會有一個獨立的程式計數器
第二、Java棧(虛擬機器棧)
Java棧就是Java中的方法執行的記憶體模型,每個方法在執行的同時都會建立一個棧幀(關於棧幀後面介紹),這個棧幀用於儲存局部變數表、運算元棧、動態連結、方法出口等資訊,每個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中入棧到出棧的過程。
註:Java棧也是線程私人的。
異常可能性:對於棧有兩種異常情況:如果線程請求的棧深度大於棧所允許的深度,將拋出StackOverflowError異常,如果虛擬機器棧可以動態拓展,在拓展的時無法申請到足夠的記憶體,將會拋出OutOfMemoryError異常
棧幀
1) 局部變數表
2) 運算元棧
3) 整數加法
4) 法返回地址
第三、本地方法棧
本地方法棧與Java棧所發揮的作用是非常相似的,它們之間的區別不過是Java棧執行Java方法,本地方法棧執行的是本地方法。
註:本地方法棧也是線程私人的
異常可能性:和Java棧一樣,可能拋出StackOverflowError和OutOfMemeryError異常
第四、Java堆
對於大多數應用來說,Java堆是Java虛擬機器所管理的記憶體中最大的一塊,在虛擬機器啟動時建立。此記憶體地區的唯一目的就是存放對象執行個體,幾乎所有的對象執行個體都在這裡分配記憶體,當然我們後面說到的記憶體回收行程的內容的時候,其實Java堆就是記憶體回收行程管理的主要區域。
註:堆是線程共用的
異常可能性:如果堆中沒有記憶體完成執行個體分配,並且堆也無法再拓展時,將會拋出OutOfMemeryError異常
第五、方法區(如果一個系統不斷地產生新的類,而沒有回收,那最終非常有可能導致永久區溢出。)
方法區它用於儲存已被虛擬機器載入的類資訊(類型資訊,欄位資訊,方法資訊,其他資訊)靜態量、即時編譯器編譯後的代碼等資料。方法區是安全執行緒的
註:方法區和堆一樣是線程共用的
異常可能性:當方法區無法滿足記憶體配置需求時,將拋出OutOfMemeryError異常
運行時常量池
用於存放編譯器產生的各種字面量和符號引用
參數配置
- -Xmx3550m:設定JVM最大堆記憶體為3550M。
- -Xms3550m:設定JVM初始堆記憶體為3550M。
- -XX:NewSize=1024m:設定新生代初始值為1024M。
- -XX:MaxNewSize=1024m:設定新生代最大值為1024M。
- -XX:PermSize=256m:設定老年代初始值為256M。
- -XX:MaxPermSize=256m:設定老年代最大值為256M。
- -XX:NewRatio=4:設定年輕代(包括1個Eden和2個Survivor區)與年老代的比值。表示新生代比老年代為1:4。
- -XX:SurvivorRatio=4:設定年輕代中Eden區與Survivor區的比值。表示2個Survivor區與1個Eden區的比值為2:4,即1個Survivor區占整個年輕代大小的1/6。
二 :Jvm記憶體回收
Java的記憶體回收機制是Java虛擬機器提供的能力,用於在空閑時間以不定時的方式動態回收無任何引用的對象佔據的記憶體空間。
堆的記憶體配置
新生代(Young Generation)(預設的Eden:Survivor = 8:1)
1.所有新產生的對象首先都是放在新生代的。新生代的目標就是儘可能快速的收集掉那些生命週期短的對象。
2.新生代記憶體按照8:1:1的比例分為一個eden區和兩個survivor(survivor0,survivor1)區。一個Eden區,兩個 Survivor區(一般而言)。大部分對象在Eden區中產生。回收時先將eden區存活對象複製到一個survivor0區,然後清空eden區,當這個survivor0區也存放滿了時,則將eden區和survivor0區存活對象複製到另一個survivor1區,然後清空eden和這個survivor0區,此時survivor0區是空的,然後將survivor0區和survivor1區交換,即保持survivor1區為空白, 如此往複。
3.當survivor1區不足以存放 eden和survivor0的存活對象時,就將存活對象直接存放到老年代。若是老年代也滿了就會觸發一次Full GC,也就是新生代、老年代都進行回收
4.新生代發生的GC也叫做Minor GC,MinorGC發生頻率比較高(不一定等Eden區滿了才觸發)
在Minor GC時會將新生代中還存活著的對象複製進一個Survivor中,然後對Eden和另一個Survivor進行清理。所以,平常可用的新生代大小為Eden的大小+一個Survivor的大小。所有的Minor GC都會觸發全世界的暫停(stop-the-world除了垃圾收集收集器線程之外的線程都被掛起),停止應用程式的線程,不過這個過程非常短暫。Eden 和Survivor區不存在記憶體片段。
當對象在 Eden出生後,在經過一次 Minor GC 後,如果對象還存活,並且能夠被另外一塊 Survivor 地區所容納( 上面已經假設為 from 地區,這裡應為 to 地區,即 to 地區有足夠的記憶體空間來儲存 Eden 和 from 地區中存活的對象 ),則使用複製演算法將這些仍然還存活的對象複製到另外一塊 Survivor 地區 ( 即 to 地區 ) 中,然後清理所使用過的 Eden 以及 Survivor 地區 ( 即from 地區 ),並且將這些對象的年齡設定為1,以後對象在 Survivor 區每熬過一次 Minor GC,就將對象的年齡 + 1,當對象的年齡達到某個值時 ( 預設是 15 歲,可以通過參數 -XX:MaxTenuringThreshold 來設定),這些對象就會成為老年代。
對於一些較大的對象 ( 即需要分配一塊較大的連續記憶體空間 ) 則是直接進入到老年代。
在執行機制上JVM提供了串列GC(SerialGC)、並行回收GC(ParallelScavenge)和並行GC(ParNew)
年老代(Old Generation)
1 . 在年輕代中經曆了N次記憶體回收後仍然存活的對象,就會被放到年老代中。因此,可以認為年老代中存放的都是一些生命週期較長的對象。
2 . 記憶體比新生代也大很多(大概比例是1:2),當老年代記憶體滿時觸發Full GC 發生頻率比較低,老年代對象存活時間比較長,存活率標記高。
什麼情況下,新生代的對象會進入老年代呢?
當Minor GC時,新生代存活的對象大於Survivor的大小時,這時一個Survivor裝不下它們,那麼它們就會進入老年代。
在新生代的每一次Minor GC 都會給在新生代中的對象+1歲,預設到15歲時就會從新生代進入老年代-XX:MaxTenuringThreshold來設定這個臨界點。
如果設定了-XX:PretenureSizeThreshold3M 那麼大於3M的對象就會直接就進入老年代。
3.finalize()方法什麼時候被調用?
l 記憶體回收行程(garbage colector)決定回收某對象時,就會運行該對象的finalize()方法 但是在Java中很不幸,如果記憶體總是充足的,那麼記憶體回收可能永遠不會進行,也就是說filalize()可能永遠不被執行,顯然指望它做收尾工作是靠不住的。 那麼finalize()究竟是做什麼的呢?
l 它最主要的用途是回收特殊渠道申請的記憶體。由於記憶體回收行程只知道那些顯示地經由new分配的記憶體空間,所以它不知道該如何釋放這塊“特殊”的記憶體地區,那麼這個時候java允許在類中定義一個由 finalize()方法
3.減少GC開銷的措施
(1)不要顯式調用System.gc()
(2)盡量減少臨時對象的使用
(3)對象不用時最好顯式置為Null
(4)盡量使用StringBuffer,而不用String來累加字串
2.觸發主GC(Garbage Collector)的條件
(1)當應用程式空閑時,即沒有應用線程在運行時,GC會被調用。因為GC在優先順序最低的線程中進行,所以當應用忙時,GC線程就不會被調用,但以下條件除外。
(2)Java堆記憶體不足時,GC會被調用。
一.如何確定某個對象是“垃圾”(無用對象)?
引用計數法
如果一個對象沒有任何引用與之關聯,則說明該對象基本不太可能在其他地方被使用到,那麼這個對象就成為可被回收的對象了。循環參考對象將無法回收
可達性分析法
該方法的基本思想是通過一系列的根對象的集合作為起點進行搜尋,從這些根對象開始,任何可以被觸及的對象都是被認為是“活動”的對象。無法被觸及的對象被認為是垃圾,因為它們不在影響程式的未來執行。
二.典型的垃圾收集演算法
1.Mark-Sweep(標記-清除)演算法
這是最基礎的記憶體回收演算法,之所以說它是最基礎的是因為它最容易實現,思想也是最簡單的。標記-清除演算法分為兩個階段:標記階段和清除階段。標記階段的任務是標記出所有需要被回收的對象,清除階段就是回收被標記的對象所佔用的空間。
可以很容易看出標記-清除演算法實現起來比較容易,但是有一個比較嚴重的問題就是容易產生記憶體片段,片段太多可能會導致後續過程中需要為大對象分配空間時無法找到足夠的空間而提前觸發新的一次垃圾收集動作。
2.Copying(複製)演算法
為瞭解決Mark-Sweep演算法的缺陷,Copying演算法就被提了出來。它將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用完了,就將還存活著的對象複製到另外一塊上面,然後再把已使用的記憶體空間一次清理掉,這樣一來就不容易出現記憶體片段的問題。問題是佔用記憶體較多
3.Mark-Compact(標記-整理)演算法
為瞭解決Copying演算法的缺陷,充分利用記憶體空間,提出了Mark-Compact演算法。該演算法標記階段和Mark-Sweep一樣,但是在完成標記之後,它不是直接清理可回收對象,而是將存活對象都向一端移動,然後清理掉端邊界以外的記憶體。
4.分代收集演算法
一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集演算法。在新生代中,每次垃圾收集時都發現有大批對象死去,那就選用複製演算法,只需要付出少量存活對象的複製成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或“標記-整理”演算法來進行回收。方法區永久代,回收方法同老年代。
老年代採用的是標記-清除或者標記-整理演算法,這兩個演算法主要看虛擬機器採用的哪個收集器,兩種演算法的區別是:標記-清除可能會產生大量連續的記憶體片段。
三.String、StringBuffer與StringBuilder之間區別
String 字串常亮,底層是數組實現的
StringBuilder:線程非安全的
StringBuffer:安全執行緒的
對於三者使用的總結:
1.如果要操作少量的資料用 = String
2.單線程操作字串緩衝區下操作大量資料 = StringBuilder
3.多線程操作字串緩衝區下操作大量資料 = StringBuffer
四.為什麼要進行記憶體回收
在C++中,對象所佔的記憶體在程式結束運行之前一直被佔用,在明確釋放之前不能分配給其它對象;而在Java中,當沒有對象引用指向原先分配給某個對象的記憶體時,該記憶體便成為垃圾。 JVM的一個系統級線程會自動釋放該記憶體塊,減輕編程的負擔。事實上,除了釋放沒用的對象,記憶體回收也可以清除記憶體記錄片段。
五.java虛擬機器類載入機制
其中類載入的過程包括了載入、驗證、準備、解析、初始化五個階段。
l 載入
載入時類載入過程的第一個階段,在載入階段,虛擬機器需要完成以下三件事情:
1、通過一個類的全限定名來擷取其定義的二進位位元組流。
2、將這個位元組流所代表的靜態儲存結構轉化為方法區的運行時資料結構。
3、在Java堆中產生一個代表這個類的java.lang.Class對象,作為對方法區中這些資料的訪問入口。
對於任意一個類,都需要由它的類載入器和這個類本身一同確定其在就Java虛擬機器中的唯一性
類載入器:啟動類載入器、擴充類載入器、應用程式類載入器、自訂類載入器
雙親委派模型:如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把請求委託給父載入器去完成,依次向上,因此,所有的類載入請求最終都應該被傳遞到頂層的啟動類載入器中,只有當父載入器在它的搜尋範圍中沒有找到所需的類時,即無法完成該載入,子載入器才會嘗試自己去載入該類。
l 驗證
驗證的目的是為了確保Class檔案中的位元組流包含的資訊符合當前虛擬機器的要求,而且不會危害虛擬機器自身的安全。
l 準備
準備階段是正式為類變數分配記憶體並設定類變數初始值(零)的階段,這些記憶體都將在方法區中分配。
l 解析
解析階段是虛擬機器將常量池中的符號引用轉化為直接引用的過程。
l 初始化
初始化是類載入過程的最後一步,此階段才開始真正執行類中定義的Java程式碼(靜態語句塊和構造器)
四.JAVA四種引用方式
強引用、軟引用、弱引用、虛引用
l 強引用:當我們使用new 這個關鍵字建立對象時被建立的對象就是強引用,記憶體回收行程就不會去回收有強引用的對象。
l 軟引用:如果一個對象具備軟引用,如果記憶體空間足夠,那麼記憶體回收行程就不會回收它,如果記憶體空間不足了,就會回收該對象。當然沒有被回收之前,該對象依然可以被程式調用。java.lang.ref.SoftReference
l 弱引用:如果一個對象只具有弱引用,只要記憶體回收行程在自己的記憶體空間中線程檢測到了,就會立即被回收,對應記憶體也會被釋放掉。java.lang.ref.WeakReference
l 虛引用:如果一個對象只具有虛引用,那麼它就和沒有任何引用一樣,隨時會被jvm當作垃圾進行回收。虛引用目的:當對象被收集器回收時收到系統通知。java.lang.ref.PhantomReference