# JVM和GC

JVM和GC

JVM運行時內存區

image

一、線程私有數據區

1、程序計數器

在JVM中,多線程是通過線程輪流切換來獲得CPU執行時間的,因此,在任一具體時刻,一個CPU的內核只會執行一條線程中的指令,因此為了能夠使得每個線程都在線程切換后能夠恢復在切換之前的程序執行位置,每個線程都需要有自己獨立的程序計數器,并且不能相互干擾,否則就會影響到程序的正確執行次序。程序計數器中記錄的是正在執行的線程的虛擬機字節碼指令的地址,字節碼的解釋器工作的時候就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令。程序計數器是每個線程私有的。

2、虛擬機棧

虛擬機棧也就是我們常說的棧。虛擬機棧是Java方法執行的內存模型。Java棧中存放的是一個個棧幀。并且是線程私有的,生命周期與線程相同,描述的是Java方法執行的內存模型:每一個方法執行的同時都會創建一個棧幀(Stack Frame),用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法的執行就是對應棧幀在虛擬機棧中的入棧、出棧的過程。下圖表示了一個Java棧的模型:

image

3、本地方法棧

本地方法棧與虛擬機棧所發揮的作用很相似,他們的區別在于虛擬機棧為執行Java代碼方法服務,而本地方法棧為Native方法服務。

二、線程共享區域

1、Java堆

Java堆可以說是虛擬機中最大的一塊內存了。它是所有線程共享的內存區域,幾乎所有的實例對象都是在這塊區域中存放。堆可以處理物理上不連續的內存空間,只要邏輯上連續的就可以。當然,隨著JIT(just in time,及時編譯技術) 編譯器的發展,所有對象在"堆"上分配也變得不那么"絕對"了。同時Java堆也是垃圾收集器管理的主要區域。由于現在收集器基本上采用的都是分代收集算法,所有Java堆又可以細分為:"新生代"和"老年代"。再細致分就是把新生代分為:Eden空間、From Survivor空間、To Survivor空間。

2、方法區

方法區在JVM中也是一個非常重要的區域,在方法區中,存儲了每個類的信息(包括類的名稱、方法信息、字段信息)、靜態變量、常量以及編譯器編譯后的代碼等。它與堆一樣,是被線程共享的區域,很容易理解,我們在寫Java代碼時,每個線程都可以訪問同一個類的靜態變量。在Class文件中除了類的字段、方法、接口等描述信息外,還有一項信息是常量池,用來存儲編譯期間生成的字面量和符號引用。

垃圾回收

哪些對象需要回收

1、引用計數法:判斷對象的引用數量

引用計數法是通過判斷對象的引用數量來決定對象是否可以被回收

給對象中添加一個引用計數器,每當有一個地方引用他時,計數器值就+1,;當引用失效時,計數器值就-1;任何時刻計數器為0的對象就是不可能在被使用。

  • 優點:

判定效率很高

  • 確定:

不會完全準確,因為如果出現兩個對象相互引用的問題就不行了,如下圖所示:

image

如上圖對象A和對象B相互引用,導致他們的引用計數都不為0,那么垃圾收集器就永遠不會回收他們。

2、可達性分析算法:判斷對象的引用鏈是否可達

通過一系列的GC Roots的對象作為起始點,從這些根節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。

image

上圖中,ObjD和ObjE都是不可用的,可以被GC回收掉。

在Java中,可作為 GC Root 的對象包括以下幾種:

  • 虛擬機棧(棧幀中的局部變量表)中引用的對象
  • 方法區中靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中Native引用的對象

垃圾收集算法

1、標記清除算法

標記清除即Mark-Sweep,是一種最簡單的收集算法。在經歷過對象判活以后,我們把需要回收的對象標記出來,然后在統一時刻回收所有被標記的對象。如圖所示:

image

黑色標記的可回收對象在回收后全部變成未使用空間,但是這樣回收后有木有發現空間碎片很多,碎片太多就會導致再分配稍微大點的空間時,找不到這樣的連續內存,從而導致GC會被頻繁調用,所以標記清除是一種基礎的垃圾收集算法,其它算法基本都是以它為基礎優化產生。

2、復制算法

復制算法的思想就是把內存分為兩塊,每次只在一邊分配內存,當一邊的內存用完了,就把所有還存活的對象復制到另一半去,這時候把原來使用過的這一邊的所有空間一次性清理掉,所以也就不存在內存碎片的問題了。缺點就是會浪費一半的內存空間。基本思路如圖:

image

其實分代GC算法在新生代區域就用了復制算法,并且也沒有分成1:1,而是8:1,也就是所謂的Eden區和survivor區,新生代中大多數對象都是“朝生夕死”的,所以在minorGC時,只把存活下來的對象全部復制到survivor區。

3、標記整理算法

上面提到的復制算法也有它的弱點,就是當對象存活率很高的時候,就會存在很多的復制操作,從而影響了效率。所以這種算法運用在老年代的話很明顯不合適,于是又有了標記整理算法,這種算法的主要思路就是把活躍對象標記出來,之后再向內存的一側移動,然后直接清理掉邊界以外的內存,具體思路如下:


image

4、分代收集算法

新生代中的對象每次回收都基本上只有10%左右的對象存活,所以需要復制的對象很少,效率還不錯。實踐中會將新生代分為一塊較大的Eden空間和兩塊較小的Surivor空間,每次使用Eden和其中一塊Survivor。當回收時,將Eden和Survivor中還存活著對象一次地復制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過的Survivor空間。HotSpot虛擬機默認的Eden和Survivor的大小比例是8:1:1。也就是每次新生代中可用內存空間為整個新生代容量的90%(80%+10%),只有10%的內存會被"浪費"。

image

對于一個大型的系統,當創建的對象和方法變量比較多時,堆內存中的對象也會比較多,如果逐一分析對象是否該回收,那么勢必造成效率低下。分代收集算法是基于這樣一個事實:不同的對象的生命周期(存活情況)是不一樣的,故而不同生命周期的對象位于堆中不同的區域,因此對堆內存不同區域采用不同的策略進行回收可以提高JVM的執行效率。當代商用虛擬機使用的都是分代收集算法:新生代對象存活率低,就采用復制算法;老年代存活率高,就采用標記清除算法或者標記整理算法。Java堆內存一般可以分為新生代、老年代和永久帶三個模塊。如下所示:

image
  • 1、新生代(Young Generation)

新生代的目標是盡可能快速收集掉那些生命周期短的對象,一般情況下,所有新生成的對象首先都是放在新生代的。新生代內存按照8:1:1的比例分成一個eden區和兩個Survivor(s0,s1)區,大部分對象在Eden區中生成。在進行垃圾回收時,先將eden區存活對象復制到s0區,然后清空eden區,當這個s0也滿了時,則將eden區和s0區存對象復制到s1區,然后清空eden和s0。此時s0區是空的,然后交換s0區和s1區的角色(即下次垃圾回收時會掃描Eden區和s1區),即保持s0區為空,如此往返。特別地,當s1區也不足以存放eden區和s0區的存活對象時,就將存活對象直接存放到老年代。如果老年代也滿了,就會觸發一次FullGC,也就是新生代、老年代都進行回收。注意,新生代發生的GC也叫MinorGC,MinorGC發生頻率比較高,不一定等到Eden區滿了才觸發。

  • 2、老年代(Old Generation)

老年代存放的都是一些生命周期長的對象,就像上面的所敘述的那樣,在新生代中經歷了N次垃圾回收后仍然存活的對象就會被放到老年代中。此外,老年代的內存也比新生代大很多,大概比例是(1:2),當老年代滿時會觸發Major GC/Full GC,老年代對象存活時間比較長,因此Major GC/Full GC發生的頻率比較低。

  • 3、永久代(Permanent Generation)

永久代主要用于存放靜態文件,如Java類、方法等。永久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,例如使用反射、動態代理、GCLib等bytecode框架時,在這種時候需要設置一個比較大的永久代空間來存放這些運行過程中新增的類。

  • 4、小結

由于對象進行了分代處理,因此垃圾回收區域、時間也不一樣。垃圾回收有兩種類型,Minor GC 和Major GC/Full GC。

Minor GC:對新生代進行回收,不會影響到老年代。因為新生代的Java對象大多死亡頻繁,所以 Minor GC 非常頻繁,一般在這里使用速度快、效率高的算法,使垃圾回收能盡快完成。

Major GC/Full GC:對整個堆進行回收,包括新生代和老年代。由于Full GC需要對整個堆進行回收,所以比Minor GC要慢,因此應該盡可能減少Full GC的次數,導致Full GC的原因包括:老年代要被寫滿、永久代被寫滿和System.gc()被顯式調用等。

更多垃圾收集算法參考這篇文章
參考

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容

  • 原文閱讀 前言 這段時間懈怠了,罪過! 最近看到有同事也開始用上了微信公眾號寫博客了,挺好的~給他們點贊,這博客我...
    碼農戲碼閱讀 6,002評論 2 31
  • JVM架構 當一個程序啟動之前,它的class會被類裝載器裝入方法區(Permanent區),執行引擎讀取方法區的...
    cocohaifang閱讀 1,684評論 0 7
  • jvm原理 Java虛擬機是整個java平臺的基石,是java技術實現硬件無關和操作系統無關的關鍵環節,是java...
    AI喬治閱讀 17,274評論 21 486
  • 1 CPU和內存的交互 了解jvm內存模型前,了解下cpu和計算機內存的交互情況。【因為Java虛擬機內存模型定義...
    Garwer閱讀 371,926評論 54 551
  • 內存溢出和內存泄漏的區別 內存溢出:out of memory,是指程序在申請內存時,沒有足夠的內存空間供其使用,...
    Aimerwhy閱讀 750評論 0 1