對於jvm架構和垃圾回收機制的拙見

前言

總所周知,java是可以跨平臺運行的,然而真正實現跨平臺的不是java語言本身,而是jvm(java虛擬機)。jvm是虛擬的,適應“一看不見的,只存在於內存中間,因爲jvm有自己的命令集,每一個不同的命令針對一個不同的cpu去做不同的翻譯,就是在不同的cpu下將程序轉換成不同的機器語言。從而實現java的跨平臺性。

1.jvm的基本架構

jvm由三部分組成:

1.類加載器:類加載器的作用是加載類文件到內存,文件格式有具體要求(類加載器只會看文件的格式是否符合規範,不會去查找文件中是否有錯,查看文件中是否有錯是執行引擎的事)

2.運行時數據區(內存):運行數據區是整個JVM 的重點。我們所有寫的程序都被加載到這裏,之後纔開始運行,由內存進行分配。

3.執行引擎(解釋器):負責解釋命令,提交操作系統執行

 

2.java類的生命週期

加載:在硬盤上查找並通過IO讀入字節碼文件

連接:

  1. 驗證:校驗字節碼文件的正確性
  2. 準備:給類的靜態變量分配內存,並賦予默認值
  3. 解析:將符號引用轉爲直接引用

初始化:對類的靜態變量初始化爲指定的值,執行靜態代碼塊

使用:在類的使用過程中依然存在三步:對象實例化、垃圾收集、對象終結

(1)對象實例化:就是執行類中構造函數的內容,如果該類存在父類JVM會通過顯示或者隱示的方式先執行父類的構造函數,在堆內存中爲父類的實例變量開闢空間,並賦予默認的初始值,然後在根據構造函數的代碼內容將真正的值賦予實例變量本身,然後,引用變量獲取對象的首地址,通過操作對象來調用實例變量和方法 
(2)垃圾收集:當對象不再被引用的時候,就會被虛擬機標上特別的垃圾記號,在堆中等待GC回收 
(3)對象的終結:對象被GC回收後,對象就不再存在,對象的生命也就走到了盡頭

卸載:即類的生命週期走到了最後一步,程序中不再有該類的引用,該類也就會被JVM執行垃圾回收,從此生命結束…

.class在jvm的加載原理就是加載——>連接——>初始化。

3.垃圾收集器(gc機制)

首先,JVM的內存結構包括五大區域:程序計數器(pc寄存器)、虛擬機棧、本地方法棧、堆區、方法區。然而對於程序計數器、虛擬機棧、本地方法棧來說他們隨線程起,隨線程滅,他們的回收時確定了的。然而不能確定的就是堆區和方法區,這部分內存的分配和回收是動態的,所以就要用到垃圾收集器。

常用的垃圾回收算法:

1.標記-清除算法​​​​​​​​​​​​​​

 標記-清除算法採用從根集合進行掃描,對存活的對象進行標記,標記完畢後,再掃描整個空間中未被標記的對象,進行回收,如下圖所示。標記-清除算法不需要進行對象的移動,只需對不存活的對象進行處理,在存活對象比較多的情況下極爲高效,但由於標記-清除算法直接回收不存活的對象,因此會造成內存碎片。

2.複製算法

  複製算法的提出是爲了克服句柄的開銷和解決內存碎片的問題。它開始時把堆分成 一個對象 面和多個空閒面, 程序從對象面爲對象分配空間,當對象滿了,基於copying算法的垃圾 收集就從根集合(GC Roots)中掃描活動對象,並將每個 活動對象複製到空閒面(使得活動對象所佔的內存之間沒有空閒洞),這樣空閒面變成了對象面,原來的對象面變成了空閒面,程序會在新的對象面中分配內存。

3.標記-整理算法

 標記-整理算法採用標記-清除算法一樣的方式進行對象的標記,但在清除時不同,在回收不存活的對象佔用的空間後,會將所有的存活對象往左端空閒空間移動,並更新對應的指針。標記-整理算法是在標記-清除算法的基礎上,又進行了對象的移動,因此成本更高,但是卻解決了內存碎片的問題。具體流程見下圖:

4.分代收集算法

    分代收集算法是使用的最多的收集算法,分兩代:年輕代,老年代。持久帶就是堆區之外的永久代。簡單來說就是對象首先都處在年輕代,當年輕代經過n次回收還存活的進入老年代,老年代存滿之後又會進行回收操作,讓出一定的內存,就這樣循環往復。年輕代和老年代是在堆區的回收,持久代是在方法區的回收。

1年輕代(Young Generation)的回收算法:

     a) 所有新生成的對象首先都是放在年輕代的。年輕代的目標就是儘可能快速的收集掉那些生命週期短的對象。

      b) 新生代內存按照8:1:1的比例分爲一個eden區和兩個survivor(survivor0,survivor1)區。一個Eden區,兩個 Survivor區(一般而言)。大部分對象在Eden區中生成。回收時先將eden區存活對象複製到一個survivor0區,然後清空eden區,當這個survivor0區也存放滿了時,則將eden區和survivor0區存活對象複製到另一個survivor1區,然後清空eden和這個survivor0區,此時survivor0區是空的,然後將survivor0區和survivor1區交換,即保持survivor1區爲空, 如此往復。

     c) 當survivor1區不足以存放 eden和survivor0的存活對象時,就將存活對象直接存放到老年代。若是老年代也滿了就會觸發一次Full GC,也就是新生代、老年代都進行回收。

      d) 新生代發生的GC也叫做Minor GC,MinorGC發生頻率比較高(不一定等Eden區滿了才觸發)。

2.年老代(Old Generation)的回收算法

      a) 在年輕代中經歷了N次垃圾回收後仍然存活的對象,就會被放到年老代中。因此,可以認爲年老代中存放的都是一些生命週期較長的對象。

      b) 內存比新生代也大很多(大概比例是1:2),當老年代內存滿時觸發Major GC即Full GC,Full GC發生頻率比較低,老年代對象存活時間比較長,存活率標記高。

3. 持久代(Permanent Generation)的回收算法

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

4.GC是什麼時候觸發的

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

1 Scavenge GC

  一般情況下,當新對象生成,並且在Eden申請空間失敗時,就會觸發Scavenge GC,對Eden區域進行GC,清除非存活對象,並且把尚且存活的對象移動到Survivor區。然後整理Survivor的兩個區。這種方式的GC是對年輕代的Eden區進行,不會影響到年老代。因爲大部分對象都是從Eden區開始的,同時Eden區不會分配的很大,所以Eden區的GC會頻繁進行。因而,一般在這裏需要使用速度快、效率高的算法,使Eden去能儘快空閒出來。

2 Full GC

  對整個堆進行整理,包括Young、Tenured和Perm。Full GC因爲需要對整個堆進行回收,所以比Scavenge GC要慢,因此應該儘可能減少Full GC的次數。在對JVM調優的過程中,很大一部分工作就是對於Full GC的調節。有如下原因可能導致Full GC:

a) 年老代(Tenured)被寫滿;

b) 持久代(Perm)被寫滿;

c) System.gc()被顯示調用;

d) 上一次GC之後Heap的各域分配策略動態變化;

 

參考:

https://www.cnblogs.com/1024Community/p/honery.html

    

​​​​​​​

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