JVM學習小記

2016-10-27 學習自 http://segmentfault.com/blog/exploring/ (感謝)

java虛擬機(Java virtual machine,JVM)

JVM 是 編譯後的 Java 程序(.class文件)和硬件系統之間的接口 ( 編譯後:javac 是收錄於 JDK 中的 Java 語言編譯器。該工具可以將後綴名爲. java 的源文件編譯爲後綴名爲. class 的可以運行於 Java 虛擬機的字節碼。)
JVM架構
JVM = 類加載器 classloader + 執行引擎 execution engine + 運行時數據區域 runtime data area

classloader

作用:裝載.class文件。classloader 把硬盤上的class 文件加載到JVM中的運行時數據區域, 但是它不負責這個類文件能否執行,而這個是 執行引擎 負責的。
classloader 有兩種裝載class的方式 (時機):
1. 隱式:運行過程中,碰到new方式生成對象時,隱式調用classLoader到JVM
2. 顯式:通過class.forname()動態加載


執行引擎

作用: 執行字節碼,或者執行本地方法

GC(Garbage Collection)

何爲GC?
垃圾回收機制是由垃圾收集器Garbage Collection GC來實現的,GC是後臺的守護進程。它的特別之處是它是一個低優先級進程,但是可以根據內存的使用情況動態的調整他的優先級。因此,它是在內存中低到一定限度時纔會自動運行,從而實現對內存的回收。這就是垃圾回收的時間不確定的原因。

GC有關的是: runtime data area 中的 heap(對象實例會存儲在這裏) 和 gabage collector方法。
程序運行期間,所有對象實例存儲在運行時數據區域的heap中,當一個對象不再被引用(使用),它就需要被收回。在GC過程中,這些不再被使用的對象從heap中收回,這樣就會有空間被循環利用。
GC爲內存中不再使用的對象進行回收,GC中調用回收的方法–收集器garbage collector. 由於GC要消耗一些資源和時間,Java 在對對象的生命週期特徵(eden or survivor)進行分析之後,採用了分代的方式進行對象的收集,以縮短GC對應用造成的暫停。

在垃圾回收器回收內存之前,還需要一些清理工作。
因爲垃圾回收gc只能回收通過new關鍵字申請的內存(在堆上),但是堆上的內存並不完全是通過new申請分配的。還有一些本地方法(一般是調用的C方法)。這部分“特殊的內存”如果不手動釋放,就會導致內存泄露,gc是無法回收這部分內存的。
所以需要在finalize中用本地方法(native method)如free操作等,再使用gc方法。顯示的GC方法是system.gc()

垃圾回收技術

方法一:引用計數法。簡單但速度很慢。缺陷是:不能處理循環引用的情況。
方法二:停止-複製(stop and copy)。效率低,需要的空間大,優點,不會產生碎片。
方法三:標記 - 清除算法 (mark and sweep)。速度較快,佔用空間少,標記清除後會產生大量的碎片。

JAVA虛擬機中是如何做的? java的做法很聰明,我們稱之爲”自適應”的垃圾回收器,或者是”自適應的、分代的、停止-複製、標記-清掃”式垃圾回收器。它會根據不同的環境和需要選擇不同的處理方式。

heap組成(run time area)

由於GC需要消耗一些資源和時間的,Java在對對象的生命週期特徵進行分析後,採用了分代的方式來進行對象的收集,即按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停.
heap 的組成有三區域/世代:(可以理解隨着時間,對象實例不斷變換heap中的等級,有點像年級)

JVM Heap區域

  • 新生代 Young Generation
    • Eden Space 任何新進入運行時數據區域的實例都會存放在此
    • S0 Suvivor Space 存在時間較長,經過垃圾回收沒有被清除的實例,就從Eden 搬到了S0
    • S1 Survivor Space 同理,存在時間更長的實例,就從S0 搬到了S1
  • 舊生代 Old Generation/tenured 同理,存在時間更長的實例,對象多次回收沒被清除,就從S1 搬到了tenured
  • Perm 存放運行時數據區的方法區
    Java 對不同的世代使用不同的 GC 算法。

    1. Minor collection:
      新生代 Young Generation 使用將 Eden 還有 Survivor 內的數據利用 semi-space 做複製收集(Copying collection), 並將原本 Survivor 內經過多次垃圾收集仍然存活的對象移動到 Tenured。
      Minor collection也稱爲YGC(Young Generation Collection)針對於New區進行的垃圾回收。當New區滿了的時候,進行YGC。默認情況下Full GC會觸發Minor GC,在PS GC時可通過設置-XX:-ScavengeBeforeFullGC來禁止觸發Minor GC

    2. Major collection 則會進行 Minor collection,Tenured 世代則進行標記壓縮收集。
      有的時候就是指Full Collection。Full Collection的時候所有的內存區都進行垃圾回收,耗費的時間比較長。系統在垃圾回收時會有明顯的暫停,而且暫停時間會因爲堆越大而越長。正常情況不應該出現大量的FGC。一般,GC首先使用New區特有的回收算法對New區進行回收,也就是YGC。然後,GC使用Old區的回收算法對Old區和Perm區進行回收。如果需要進行Compact,各個區分開進行壓縮。

這個搬運工作都是GC 完成的,這也是garbage collector 的名字來源,而不是叫garbage cleaner. GC負責在heap中搬運實例,以及收回存儲空間。

JVM server模式和client模式:JVM如果不指定-server或-client選項,JVM會在啓動的時候根據硬件環境判斷以server模式啓動還是以client模式啓動(適用於Java 5及以上版本)。
JVM工作在server模式可以大大提高性能,但應用的啓動會比client模式慢大概10%。當該參數不指定時,虛擬機啓動檢測主機是否爲服務器,如果是,則以server模式啓動,否則以client模式啓動,Java 5檢測的根據是至少2個CPU和最低2GB內存。
當JVM用於啓動GUI界面的交互應用時適合於使用client模式,當JVM用於運行服務器後臺程序時建議用server模式。
JVM 堆內存:Java堆由Perm區和Heap區組成,Heap區由新生代 Young Generation區和舊生代 Old Generation/tenured區組成,New區由Eden區、From區和To區(Survivor)組成。。
Eden區用於存放新生成的對象。Eden中的對象生命不會超過一次Minor GC。

  • New區的幾種Collector
    1. 串行GC(Serial Copying)
      client模式下的默認GC方式,也可使用-XX:+UseSerialGC指定。
    2. 並行回收GC(Parallel Scavenge)
      server模式下的默認GC方式,也可用-XX:+UseParallelGC強制指定。
      採用PS時,默認情況下JVM會在運行時動態調整Eden:S0:S1的比例,如果不希望自動調整可以使用-XX:-UseAdaptiveSizePolicy參數,內存分配和回收的算法和串行相同,唯一不同僅在於回收時爲多線程。
    3. 並行GC(ParNew)
      CMS GC時默認採用,也可以採用-XX:+UseParNewGC指定。
      內存分配、回收和PS相同,不同的僅在於會收拾會配合CMS做些處理。
  • Old區的幾種Collector
    1. 串行GC(Serial MSC)
      client模式下的默認GC方式,可通過-XX:+UseSerialGC強制指定。每次進行全部回收,進行Compact,非常耗費時間。
    2. 並行GC(Parallel MSC)
      server模式下的默認GC方式,也可用-XX:+UseParallelGC=強制指定。可以在選項後加等號來制定並行的線程數。
    3. 併發GC(CMS)線上環境採用的GC方式,也就是Realese環境的方式
      使用CMS是爲了減少GC執行時的停頓時間,垃圾回收線程和應用線程同時執行,可以使用-XX:+UseConcMarkSweepGC=指定使用,後邊接等號指定併發線程數。CMS每次回收只停頓很短的時間,分別在開始的時候(Initial Marking),和中間(Final Marking)的時候,第二次時間略長。具體CMS的過程可以參考相關文檔。JStat中將Initial Mark和Remark都統計成了FGC。
      CMS一個比較大的問題是碎片和浮動垃圾問題(Floating Gabage)。碎片是由於CMS默認不對內存進行Compact所致,可以通過-XX:+UseCMSCompactAtFullCollection。

總體來講,Old區的大小較大,垃圾回收算法較費時間,導致較長時間的應用線程停止工作,而且需要進行Compact,所以不應該出現較多Major GC。Major GC的時間常常是Minor GC的幾十倍。JVM內存調優的重點,減少Major GC 的次數,因爲爲Major GC 會暫停程序比較長的時間,如果Major GC 的次數比較多,意味着應用程序的JVM內存參數需要進行調整。

JVM GC

GC工作原理

JVM 分別對新生代和舊生代採用不同的垃圾回收機制

何爲垃圾?
Java中那些不可達的對象就會變成垃圾。那麼什麼叫做不可達?其實就是沒有辦法再引用到該對象了。主要有以下情況使對象變爲垃圾:
1. 對非線程的對象來說,所有的活動線程都不能訪問該對象,那麼該對象就會變爲垃圾。
2. 對線程對象來說,滿足上面的條件,且線程未啓動或者已停止。

JVM中將對象的引用分爲了四種類型,不同的對象引用類型會造成GC採用不同的方法進行回收:
(1)強引用:默認情況下,對象採用的均爲強引用
(GC不會回收)
(2)軟引用:軟引用是Java中提供的一種比較適合於緩存場景的應用
(只有在內存不夠用的情況下才會被GC)
(3)弱引用:在GC時一定會被GC回收
(4)虛引用:在GC時一定會被GC回收


runtime data area

JVM 運行時數據區 (JVM Runtime Area) 其實就是指 JVM 在運行期間,其對JVM內存空間的劃分和分配。JVM在運行時將數據劃分爲了6個區域來存儲。
run time Area

程序員寫的所有程序都被加載到運行時數據區域中,不同類別存放在heap, java stack, native method stack, PC register, method area.

  1. PC程序計數器(PC register)
    一塊較小的內存空間,可以看做是當前線程所執行的字節碼的行號指示器, NAMELY存儲每個線程下一步將執行的JVM指令,如該方法爲native的,則PC寄存器中不存儲任何信息。Java 的多線程機制離不開程序計數器,每個線程都有一個自己的PC,以便完成不同線程上下文環境的切換。

  2. java虛擬機棧(stack)
    與 PC 一樣,java 虛擬機棧也是線程私有的。每一個 JVM 線程都有自己的 java 虛擬機棧,這個棧與線程同時創建,它的生命週期與線程相同。虛擬機棧描述的是Java 方法執行的內存模型:
    當線程執行一個方法時,就會隨之創建一個對應的棧幀,並將建立的棧幀壓棧。當方法執行完畢之後,便會將棧幀出棧。因此可知,線程當前執行的方法所對應的棧幀必定位於Java棧的頂部。講到這裏,大家就應該會明白爲什麼 在 使用 遞歸方法的時候容易導致棧內存溢出的現象了以及爲什麼棧區的空間不用程序員去管理了(當然在Java中,程序員基本不用關係到內存分配和釋放的事情,因爲Java有自己的垃圾回收機制),這部分空間的分配和釋放都是由系統自動實施的。對於所有的程序設計語言來說,棧這部分空間對程序員來說是不透明的。
    每個棧幀對應一個被調用的方法,在棧幀中包括局部變量表(Local Variables)、操作數棧(Operand Stack)、指向當前方法所屬的類的運行時常量池(運行時常量池的概念在方法區部分會談到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些額外的附加信息。下圖表示了一個Java棧的模型:
    JAVA棧

    • 局部變量表,顧名思義,就是用來存儲方法中的局部變量(包括在方法中聲明的非靜態變量以及函數形參)。對於基本數據類型的變量,則直接存儲它的值,對於引用類型的變量,則存的是指向對象的引用。局部變量表的大小在編譯器就可以確定其大小了,因此在程序執行期間局部變量表的大小是不會改變的。

    • 操作數棧,想必學過數據結構中的棧的朋友想必對表達式求值問題不會陌生,棧最典型的一個應用就是用來對表達式求值。想想一個線程執行方法的過程中,實際上就是不斷執行語句的過程,而歸根到底就是進行計算的過程。因此可以這麼說,程序中的所有計算過程都是在藉助於操作數棧來完成的。

    • 指向運行時常量池的引用,因爲在方法執行的過程中有可能需要用到類中的常量,所以必須要有一個引用指向運行時常量。

    • 方法返回地址,當一個方法執行完畢之後,要返回之前調用它的地方,因此在棧幀中必須保存一個方法返回地址。

  3. 本地方法棧(native method stack):與虛擬機棧的作用相似,虛擬機棧爲虛擬機執行執行java方法服務,而本地方法棧則爲虛擬機使用到的本地方法服務。

  4. Java堆(heap):被所有線程共享的一塊存儲區域,在虛擬機啓動時創建,它是JVM用來存儲對象實例以及數組值的區域,可以認爲Java中所有通過new創建的對象的內存都在此分配。

Java堆在JVM啓動的時候就被創建,堆中儲存了各種對象,這些對象被自動管理內存系統(Automatic Storage Management System,也即是常說的 “Garbage Collector(垃圾回收器)”)所管理。這些對象無需、也無法顯示地被銷燬。

JVM將Heap分爲兩塊:新生代New Generation和舊生代Old Generation

Note:

堆在JVM是所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,這也是new開銷比較大的原因。
鑑於上面的原因,Sun Hotspot JVM爲了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間,這塊空間又稱爲TLAB
TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效
5、方法區(method area)
方法區和堆區域一樣,是各個線程共享的內存區域,它用於存儲每一個類的結構信息,例如運行時常量池,成員變量和方法數據,構造函數和普通函數的字節碼內容,還包括一些在類、實例、接口初始化時用到的特殊方法。當開發人員在程序中通過Class對象中的getName、isInstance等方法獲取信息時,這些數據都來自方法區。

方法區也是全局共享的,在虛擬機啓動時候創建。在一定條件下它也會被GC。這塊區域對應Permanent Generation 持久代。 XX:PermSize指定大小。

6、運行時常量池
其空間從方法區中分配,存放的爲類中固定的常量信息、方法和域的引用信息。

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