JVM-簡介及垃圾回收

一、JVM java虛擬機器

1、JVM

java虛擬機器是一個可執行java位元組碼的虛擬機器程序。Java虛擬機器本質上就是一個程式,java原始檔被編譯成能被java虛擬機器執行的位元組碼檔案,當它在命令列上啟動的時候,就開始執行儲存在某位元組碼檔案中的指令。Java語言的可移植性正是建立在Java虛擬機器的基礎上。任何平臺只要裝有針對於該平臺的Java虛擬機器,位元組碼檔案(。class)就可以在該平臺上執行。這就是“一次編譯,多次執行”。

java檔案,透過編譯器變成了。class檔案,接下來類載入器又將這些class檔案載入到JVM中。其實可以一句話來解釋,類的載入指的是將類的。class檔案二進位制資料讀入到記憶體中,將其放在執行時資料區的方法區內,

然後在堆區建立一個 java。lang。Class物件,用來封裝類在方法區內的資料結構。

2、jvm主要組成部分及其作用

我們常說的jvm的主要組成,指的是執行時資料區。但是,其他它還包括其他部分,執行引擎、本地介面、類載入子系統

JVM-簡介及垃圾回收

JVM-簡介及垃圾回收

jvm主要構成:方法區、Java堆、Java虛擬機器棧、本地方法區、程式計數器等。

JAVA Heap:堆,放的是new的物件,陣列,大資料的東西;執行時的資料

JVM Stack: jvm棧,控制方法怎麼排程,控制應用程式怎麼執行的

Native Method Stack:本地方法棧,本地的方法棧,呼叫本地服務

Method Area:方法區,放的是執行時的方法

其中方法區和堆是由所有執行緒共享,

而Java棧、本地方法區、程式計數器是執行緒私有。

我們常說的記憶體溢位,指的就是 java堆(Heap) 記憶體不夠了。

JVM Stack jvm棧區,每啟動個執行緒,jvm就為該執行緒分配一個棧區,執行緒呼叫方法時和方法返回時進行入棧和出棧的操作。

Native Stack 本地方法棧區,與jvm stack類似,不過此區域是為呼叫本地方法服務的

Java Heap java的所有物件例項,陣列等。

程式計數暫存器 ,每個執行緒自己的計數暫存器,儲存當前執行緒執行位元組碼的地址。

Program Counter Register:程式計數器:

每個執行緒在建立後,都會產生自己的程式計數器和棧幀。程式計數器用來存放執行指令的偏移量和行號指示器等。

執行緒執行或者恢復都要依賴程式計數器。程式計數器在各個執行緒之間互不影響。此區域也不會發生記憶體溢位異常,程式計數器是佔用空間最小的記憶體區域,不會出現OOM。

主要記錄當前執行緒正在執行的方法的指令地址。方便執行緒切換後能恢復到下一條指令的位置,如果是執行native方法,則該計數器為空。

程式計數器(後文簡稱PCR)有兩個作用:

位元組碼直譯器透過改變PCR依次讀取指令,實現程式碼的流程控制,如順序執行、選擇、迴圈、異常處理

多執行緒情況下,PCR用於記錄當前執行緒的執行位置,從而當執行緒被切換回來的時候,能夠知道該執行緒上次執行到哪兒了。

JVM-簡介及垃圾回收

JVM Stack:jvm棧

java虛擬機器棧也叫執行緒棧

虛擬機器棧包含:

1、區域性變量表

2、動態連線

3、操作棧

4、方法返回地址

1、區域性變量表

存放方法引數和區域性變數

相對於類屬性的變數準備階段和初始化階段,區域性變數沒有準備階段,必需顯式初始化。

如果是非靜態方法,則在index[0]位置儲存的是方法所屬物件的例項引用,隨後儲存的是引數和區域性變數。

2、動態連線

每個棧幀中包含一個在常量池中,對當前方法的引用。目的是支援方法呼叫過程的動態連線。

3、方法返回地址:

方法執行的時候,有兩種退出情況:

正常退出

正常執行到任何方法的返回位元組碼指令,如RETURN、IRETURN等。

異常退出

無論何種,都將返回至方法當前被呼叫的位置。方法退出的過程相當於彈出當前棧幀。

3、操作棧

本地方法棧基本上等同於java虛擬機器棧。

堆和棧是程式執行的關鍵,很有必要把他們癿關係說清楚。

JVM-簡介及垃圾回收

棧是執行時的單位,而堆是儲存的單位。

棧解決程式的執行問題,即程式如何執行,或者說如何處理資料;

堆解決的是資料儲存的問題,即數 據怎麼放、放在哪兒。

在 Java 中一個執行緒就會相應有一個執行緒棧與之對應,這點很容易理解,因為不同的執行緒執行邏輯有 所不同,因此需要一個獨立的執行緒棧。

而堆則是所有執行緒共享的。

棧因為是執行單位,因此裡面儲存 的資訊都是跟當前執行緒(或程式)相關資訊的。包括區域性變數、程式執行狀態、方法返回值等等;而 堆只負責儲存物件資訊。

元資料區(Matespace):

包括:

常量池、類元資訊、方法元資訊

園區

JDK1。8之前叫持久代;1。8之後叫園區。

園區放的是方法常量

(以下非必需)

為什麼要把堆和棧區分出來呢?棧中不是也可以儲存資料嗎?

第一,從軟體設計的角度看,棧代表了處理邏輯,而堆代表了資料。這樣分開,使得處理邏輯更為清 晰。分而治之的思想。這種隔離、模組化的思想在軟體設計的方方面面都有體現。

第二,堆與棧的分離,使得堆中的內容可以被多個棧共享(也可以理解為多個執行緒訪問同一個物件)。這種共享的收益是很多的。一方面這種共享提供了一種有效的資料互動方式(如:共享記憶體),另一方 面,堆中的共享常量和快取可以被所有棧訪問,節省了空間。

第三,棧因為執行時的需要,比如儲存系統執行的上下文,需要進行地址段的劃分。由於棧只能向上 增長,因此就會限制住棧儲存內容的能力。而堆不同,堆中的物件是可以根據需要動態增長的,因此 棧與堆的拆分,使得動態增長成為可能,相應棧中只需記錄堆中的一個地址即可。

第四,面向物件就是堆與棧的完美結合。其實,面向物件方式的程式與以前結構化的程式在執行上沒 有任何區別。但是,面向物件的引入,使得對待問題的思考方式發生了改變,而更接近於自然方式的思考。

當我們把物件拆開,你會發現,物件的屬性其實就是資料,存放在堆中;而物件的行為(方法), 就是執行邏輯,放在棧中。

我們在編寫物件的時候,其實即編寫了資料結構,也編寫了處理資料的邏輯。

在 Java 中,Main 函式就是棧的起始點,也是程式的起始點。

程式要執行總是有一個起點的。同 C 語言一樣,java 中的 Main 就是那個起點。無論什麼java 程式, 找到 main 就找到了程式執行的入口:

堆:

執行時的資料,也就是new的物件和一些陣列。

執行時最大的資料,就在堆內。

JVM-簡介及垃圾回收

2、Jvm heap記憶體空間劃分

堆內(放的是物件,陣列)=臨時資料,用完就沒啦

年輕代:eden +s1 +s0(eden:s1 :s0預設比例:8:1:1 )

老年代:(年輕代:老年代預設比例:1:2)

s0與s1大小相等,位置互換

堆外(放的是方法,類,常量)

Eden(伊甸園區):

所有新new的物件和陣列,是在伊甸園區產生的,記憶體分配是在伊甸園區進行的。

在伊甸園區new 一個物件,如果這個物件被用到了,那麼他的引用就+1。應用的越多,佔記憶體越多。

當伊甸園區記憶體被佔滿時,就不能再new物件,程式依賴物件,所以程式也就不能執行。java應用程式為了能繼續執行,就要幹掉那些不被引用的物件,對這些不被引用的物件進行垃圾回收。

現代虛擬機器一般使用的記憶體回收策略是分代收集,即把物件分為兩代,新生代(年輕代)使用複製演算法回收記憶體,老年代使用標誌-整理算方法回收記憶體。

垃圾回收的時候,如何判斷該物件是否可以被回收:

有2種演算法

演算法一:引用計數(已經被廢棄了)

引用計數,物件被建立後,有個計數器。只要物件被引用,那麼計數器就+1,再次被引用,就再+1,如果方法執行完成,物件不被引用了,計數器就-1。

當計數器為0時,該物件沒有被引用,就可以被垃圾回收了。

引用計數演算法,有一個弊端,就是在執行遞迴的時候,就沒有辦法計算了。

演算法二:尋根判斷

尋根判斷,從物件的根結點開始去找,是否被引用,如果跟節點都沒有引用,那麼就是沒有被引用。

被引用的物件,稱為存活物件。

未被引用的物件,稱為非存活物件。

當伊甸園區被佔滿時,觸發GC(垃圾回收 Garbage Collection)。

GC分兩步,第1步標記,對非存活物件進行標記。第2步清掃,將非存活物件從記憶體中幹掉,釋放記憶體,同時將存活的物件移到存活區中。

YGC

發生在年輕代。

在YGC之前,伊甸園區是滿的,無法new物件,程式無法執行。

YGC之後,伊甸園區就空了,就可以繼續new物件。

YGC整個流程:

第一次gc,,對存活的物件進行標記,然後將Eden區的存活物件,存活區S0

第二次gc,使用尋根判斷的演算法,對存活的物件進行標記,然後將Eden區的存活物件,移到存活區S0

步驟1:new的物件、陣列,直接進入eden園區。

步驟2:eden園區滿了,觸發ygc,對【eden園區】的物件和陣列進行標記(使用尋根判斷的演算法),清掃(將沒有引用的物件幹掉,被引用的移到其中一個存活區s0);

eden園區第二次滿了,再次觸發ygc,對【eden園區和存活區s0】的物件和陣列進行標記,清掃將沒有引用的幹掉,被引用的物件和陣列移到在另一個存活區s1(這裡用的是複製演算法)

步驟3:fgc,全gc,堆記憶體和持久代均gc。將沒有的幹掉,有用的放到老年代。

年輕代裡面的兩個存活區,大小相等,且同一時間只有一個存活區有物件,另一個存活區為空。gc之前,存活區s0有物件,s1為空,一次gc之後,s0為空,s1存放物件。

JVM-簡介及垃圾回收

物件進入老年代原則:

大物件直接進入老年代

長期存活的物件進入老年代

動態年齡判斷

一次Young GC時資料放到存活區,但是存活區滿了導致放不下去此時直接進入老年代(儘可能避免這種情況)

JVM-簡介及垃圾回收

1、大物件直接進入老年代

在堆中分配的大物件直接挪到老年代

大物件是指需要大量連續記憶體空間的物件,例如很長的字串以及陣列。

虛擬機器設定了一個-XX:PretenureSizeThreshold引數,有個預設值,令大於這個設定的物件直接在老年代分配,這個值是可以修改的。

目的就是為了防止大物件在Eden空間和Survivor空間來回大量複製,大物件很容易把伊甸園區佔滿,導致YGC頻繁。

2、長期存活的物件進入老年代(伴隨YGC產生的)

虛擬機器給每個物件定義了一個物件年齡(Age)計數器,如果物件在Eden區出生並經過第一次YGC/Mintor GC後仍然存活,並且能被Survivor接納,並被移動到Survivor空間上,那麼該物件年齡將被設定為1。

物件在Survivor區中每熬過一次YGC/Minor GC,年齡就加一,當他的年齡增加到一定程度,就會被移動到老年代(年齡值預設為15)。

物件晉升老年代的閾值可以透過-XX:MaxTenuringThreshold設定。

3、動態年齡判斷並進入老年代(伴隨YGC產生的)

為了更好的適應不同程式的記憶體狀況,虛擬機器並不是永遠要求物件的年齡必須達到MaxTenuringThreshold才會晉升到老年代。

如果在Survivor空間中相同年齡的所有物件大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的物件就可以直接進入老年代,無需達到MaxTensuringThreshold的要求年齡。

(相同age物件大小之和>1/2存活區(即s0或者s1)則所有>=age的物件就會進入老年代)

舉個栗子:

比如:MaxTenuringThreshold 為15,Survivor記憶體大小為100M。age 為1的,所有物件大小之和為10M。age 為5的,所有物件大小之和為51M。age為6的,所有物件大小之和為5M。

因為age 為5的物件所佔記憶體之和已經超過了Survivor空間的一半,所以age為5,和age大於5的物件,都要移到老年代(沒有age達到15的限制)

4、一次Young GC時資料放到存活區,但是存活區滿了導致放不下去此時直接進入老年代

儘可能避免這種情況,如果物件直接從Eden區到老年代,那麼存活區就沒有什麼存在的意義了,之所以設定存活區,就是為了將沒有引用的物件更早的回收掉,將記憶體騰出來。

Full GC

當老年代滿了之後,觸發Full GC。

Full GC 範圍:

整個堆記憶體(年輕代+老年代)+園資料區

對堆的回收:

標記——清掃

對堆標記,物件是否被引,被引用的為存活物件。

對園資料回收:

標記——清掃

標記類,哪些類失效了。

回收,將失效的類解除安裝掉。

FGC時長,大小跟記憶體大小有關係,記憶體大,則FGC時間長。

時長不太好控制,但是我們可以控制FGC頻次。

當老年代和年輕代,被存活的物件佔滿時(GC也不能將這些存活的物件清理掉),在Eden就不能再建立新的物件,導致OOM。

一、OOM含義:

OOM,全稱“Out Of Memory”,意思是“記憶體用完了”。它來源於java。lang。OutOfMemoryError。

官方介紹為當JVM因為沒有足夠的記憶體來為物件分配空間並且垃圾回收器也已經沒有空間可回收時,就會丟擲 java。lang。OutOfMemoryError :···

(注意:這是個很嚴重的問題,因為這個問題已經嚴重到不足以被應用處理)。

gc的觸發條件

觸發條件:

YGC:

有且只有這一種情況,eden滿了,觸發gc

FGC:

1、老年代滿了

2、園區某些類已經失效了,在載入的找不到這個類,也會觸發FGC,去解除安裝這個類,釋放這個類。

3、空間擔保原則(主要觸發fgc的方式)

4、程式碼裡面顯示呼叫

5、jmap dump

記憶體擔保機制

現代虛擬機器把新生代分為三個區域,一個Eden區域,兩個Survivor區域,Eden區域與Survivor區域的比例大小是8:1,虛擬機器在YGC/Minor GC時在新生代採用複製演算法,將存活物件複製到一個Survivor上面,如果Survivor空間不夠用時,就需要老年代進行分配擔保。

在發生Minor GC之前虛擬機器會先檢查老年代最大可用的連續空間是否大於新生代物件的總空間。如果這個條件成立,那麼Minor GC可以確保是安全的。如果不成立,虛擬機器會檢視HandlePromotionFailure設定值是否允許擔保失敗。如果允許,虛擬機器會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代的平均年齡(因為事先不知道存活物件的記憶體空間,所以取了平均值)。若果大於,虛擬機器會嘗試進行一次Minor GC,但是這次Minor GC存在風險。如果小於,或者HandlePromotionFailure不允許擔保,那這次也要改為Full GC

空間擔保原則:

每次ygc的時候,都會往老年代裡放物件。

根據歷史每次往老年代放的物件大小,根據一個演算法,估算這一次要放物件大小。估算的數值,小於老年代剩餘記憶體,就執行ygc。

如果大於老年代剩餘空間,放棄本次ygc,直接fgc。

程式碼裡面顯示呼叫

程式碼裡寫了system。gc或者get run time 。gc

fgc特別消耗cpu,儘可能把FGC頻次減少,最起碼要一小時以上

java程序之間以及跟JVM是什麼關係

1。程式的執行是以程序在記憶體中的執行形式體現的。當你啟動一個程式時,系統會呼叫其對應程序進入記憶體執行,圖中程序的pid即為程序的唯一識別符號。然後程序之間是併發執行的。準確的說,你啟動的是java程式,但系統執行的是程序,因為程式是靜態的,程序才是動態的,也就是程式並不會進入記憶體執行,而是其對應程序進入記憶體執行。

2。是公用一個JVM的,這個就類似你電腦自己的作業系統,開啟兩個程式肯定是在同一個系統記憶體中執行的,原因就是我問題一中說的程序是併發執行的。

命令列啟動的java程式是共用一個jvm的,啟動一個程式就是在jvm中開啟一個程序,每個程序至少有一個執行緒,當然可以有多個執行緒,

執行緒之間通訊比較簡單,就像java書上講的一樣,但程序間的通訊複雜點,如管道、記憶體對映、記憶體共享、訊息佇列、socket等,你可以簡單理解為兩個程序間沒關係

2個程序肯定是2個jvm例項

二、監控GC命令

1、jstat監控Java程序的GC情況

登陸伺服器

檢視java程序

ps -ef|grep java

監控fgc次數和時間

jstat -gcutil 22893 2000

jstat -gcutil [Java的pid] 2000 20 (間隔2s,總共列印20次)

jstat -gcutil 22278 2000

檢視gc的情況(記憶體使用百分比及gc的總時間,gc次數)

——————END——————-

TAG: 物件執行緒記憶體存活Java