學習筆記—JVM

JVM結構

JVM整體結構圖

類加載子系統與方法區:

  類加載子系統負責從文件系統和網絡中加載Class信息,加載的類信息存放於一塊稱爲方法區的內存空間。java

  除了類信息外,方法區中還可能會存放運行時常量池信息,包括字符串字面量和數字常量(這部分常量信息是Class文件中常量池部分的內存映射)算法

Java堆:

  java堆在虛擬機啓動時創建,它是java程序最主要的內存工做區域。服務器

  幾乎全部的java對象實例都存放在java堆中。堆空間是全部線程共享的。網絡

直接內存:

  java的NIO庫容許使用直接內存。多線程

  直接內存是在java堆外的、直接向系統申請的內存空間。併發

  一般訪問直接內存的速度會優於java堆。jvm

  出於對性能的考慮,讀寫頻繁的場合可能會考慮使用直接內存。函數

  因爲直接內存在java堆外,所以它的大小不會受限於Xmx指定的最大堆大小,可是系統內存是有限的,java堆和直接內存的總和依然受限於操做系統能給出的最大內存。工具

垃圾回收系統:

  垃圾回收器能夠對方法區、java堆和直接內存進行回收。其中,java堆是垃圾收集器的工做重點。性能

  和C/C++不一樣,java中全部的對象空間釋放都是隱式的,java中沒有相似free()或者delete()這樣的函數釋放指定的內存區域。

  對於再也不使用的垃圾對象,垃圾回收系統會在後臺查找、標識並釋放對象,完成java堆、方法區和直接內存中的全自動化管理。

java棧:

  每個java虛擬機線程都有一個私有的java棧。

  一個線程的java棧在線程建立的時候被建立。

  java棧中保存着幀信息,保存着局部變量、方法參數,同時和java方法的調用、返回密切相關。

本地方法棧:

  和java棧很是相似。

  java棧用於方法的調用,而本地方法棧則用於本地方法的調用。

  做爲對java虛擬機的重要擴展,java虛擬機容許java直接調用本地方法(一般使用C編寫)

PC(Program Counter):

  PC寄存器也是每個線程私有的空間。

  java虛擬機會爲每個java線程建立PC寄存器。

  在任意時刻,一個Java線程老是在執行一個方法,這個正在被執行的方法稱爲當前方法。若是當前方法不是本地方法,PC寄存器就會指向當前正在被執行的指令。若是當前方法是本地方法,那麼PC寄存器的值就是undefined。

執行引擎:

  負責執行虛擬機的字節碼。

  現代虛擬機爲了提升執行效率,會使用即便編譯(just in time)技術將方法編譯成機器碼後再執行。

 

JVM堆結構及分代

  堆內存是java虛擬機管理的內存中最大的一塊,也是垃圾回收最頻繁的一塊區域,程序中全部的對象實例都存放在堆內存中。

  給堆內存分代是爲了提升對象內存分配和垃圾回收的效率。

  若是堆內存沒有區域劃分,全部的新建立的對象和生命週期很長的對象放在一塊兒,隨着程序的執行,堆內存須要頻繁進行垃圾收集,而每次回收都要遍歷全部的對象,所花費的時間代價是巨大的,也會嚴重影響GC效率。

  

  Java虛擬機根據對象存活的週期不一樣,把堆內存分爲新生代、老年代和永久代(於JDK8中把存放元數據中的永久內存從堆內存中移到了本地內存(native memory)

  新建立的對象會在新生代中分配內存,通過屢次回收仍然存活下來的對象放在老年代。

  新生代中的對象存活時間短,須要頻繁地進行垃圾回收以保證無用對象儘早被釋放掉;老年代中對象生命週期長,內存回收的頻率相對較低,不須要頻繁進行回收。不一樣年代採用各自合適的垃圾回收算法,能夠大大提升回收效率。

 

新生代(Young Generation)

  新生代主要存放新生成的對象,內存大小相對會比較小,垃圾回收會比較頻繁。且垃圾回收效率高,一般進行一次垃圾收集通常能夠回收70% ~ 95%的空間。

  HotSpot JVM把新生代代分爲了三部分:1個Eden區和2個Survivor區(分別叫from和to),默認比例是 8:1:1。這樣劃分的目的是由於HotSpot採用複製算法來回收新生代。(複製算法的基本思想就是將內存分爲兩塊,每次只用其中一塊,當這一塊內存用完,就將還活着的對象複製到另一塊上面。複製算法不會產生內存碎片。)新生成的對象在Eden區分配(一些大對象除外),這些對象通過第一次Minor GC後,若是仍然存活,將會被移到Survivor區。當Eden區沒有足夠的空間進行分配時,虛擬機將發起一次Minor GC。

  在GC開始的時候,對象只會存在於Eden區和名爲「From」的Survivor0區,Survivor1區「To」是空的(做爲保留區域)。GC進行時,Eden區中全部存活的對象都會被複制到「To」Survivor1區,而在「From」Surivor0區中,仍存活的對象會根據他們的年齡值來決定去向。新生代中的對象每熬過一輪垃圾回收,年齡值就加1,年齡值達到年齡閥值(默認爲15,能夠經過-XX:MaxTenuringThreshold來設置,GC分代年齡存儲在對象的header中)的對象會被移到老年代中,沒有達到閥值的對象會被複制到To Survivor1區。通過此次GC後,Eden區和「From"Surivor0區已經被清空。接着將」To「區與」From「區交換,確保」To「區在一輪GC後是空的。Minor GC會一直重複這樣的過程,直到「To」區被填滿,沒有足夠的空間存放上一次新生代收集下來的存活對象時,會將全部的對象移入老年代。

 

老年代(Old Generation)

  老年代主要存放老年代的空間用於存放長時間倖存的對象,即在新生代中經歷了屢次GC後仍然存活下來的對象。老年代中的對象生命週期較長,存活率比較高,在老年代中進行GC的頻率相對而言較低,回收的速度也比較慢。

  一般當老年代內存被佔滿時進行一次Major GC。相較於minor GC, Major GC的執行次數要比minor GC要少不少,同時,Major Gc 執行的時間較Minor Gc要長。

 

永久代(Permanent)

  於JDK8中把存放元數據中的永久內存從堆內存中移到了本地內存(native memory)。

 

 

 

JVM垃圾回收算法及收集器

垃圾回收常見算法

引用計數(Reference Counting)

  比較古老的回收算法,原理是此對象有一個引用,即增長一個計數,刪除一個引用則減小一個計數。

  垃圾回收時,只用收集計數爲0的對象,此算法最致命的是沒法處理循環引用的問題。

複製(Copying)

  此算法把內存空間劃爲兩個相等的區域,每次只使用其中一個區域。

  垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另一個區域中,此算法每次只處理正在使用中的對象,所以複製成本較小,同時複製過去之後還能進行相應的內存管理,不會出現」碎片「問題。

  固然,此算法的缺點也很明顯,就是須要兩倍內存空間。

標記-清除(Mark - Sweep)

  此算法執行分兩階段,第一階段從引用根結點開始標記全部被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法須要暫停整個應用,同時,會產生內存碎片。

標記-整理(Mark-Compact)

  此算法結合了」標記-清除「和」複製「兩個算法的優勢。也是分兩個階段,第一階段從根結點開始標記全部被引用對象,第二階段遍歷整個堆,清除未標記對象而且把存活對象」壓縮「到堆的其中一塊,按順序排放。此算法避免了」標記-清除「的碎片問題,同時也避免了」複製「算法的空間問題。

 

jvm中垃圾收集器

Scavenge Gc(次收集)和 Full GC(全收集)的區別

  新生代GC(Scavenge GC):Scavenge GC指發生在新生代的GC,由於新生代的java對象生命週期短,因此Scavenge GC很是頻繁,通常回收速度也比較快。當Eden空間不足爲對象分配內存時,會觸發Scavenge GC。

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

  老年代GC(Full GC/Major GC):Full GC指發生在老年代的GC,出現了Full GC通常會伴隨着至少一次的Minor GC(老年代對象大部分是Minor GC過程當中重新生代進入老年代),好比分配擔保失敗。Full GC的速度通常會比Minor GC慢 10 倍以上。當老年代內存不足或者顯式調用System.gc()方法時,會觸發Full Gc。

次收集

  當年輕代堆空間緊張時會被觸發

  相對於全收集而言,收集間隔較短

全收集

  當老年代或者持久代堆空間滿了,會觸發全收集操做

  可使用System.gc()方法來顯式的啓動全收集

  全收集通常根據堆大小的不一樣,須要的時間不盡相同,但通常會比較長。

 

分代垃圾回收器

新生代收集器

串行收集器(Serial)

  Serial收集器是JAVA虛擬機中最基本、歷史最悠久的收集器,在JDK 1.3.1以前是JAVA虛擬機新生代收集的惟一選擇。Serial收集器是一個,但它的「單線程」的意義並不只僅是說明它只會使用一個CPU或一條收集線程去完成垃圾收集工做,更重要的是在它進行垃圾收集時,必須暫停其餘全部的工做線程,直到它收集結束。

  Serial收集器到JDK1.7爲止,它依然是JAVA虛擬機運行在Client模式下的默認新生代收集器。它也有着優於其餘收集器的地方:簡單而高效(與其餘收集器的單線程比),對於限定單個CPU的環境來講,Serial收集器因爲沒有線程交互的開銷,專心作垃圾收集天然能夠得到最高的單線程收集效率。在用戶的桌面應用場景中,分配給虛擬機管理的內存通常來講不會很大,收集幾十兆甚至一兩百兆的新生代(僅僅是新生代使用的內存,桌面應用基本上不會再大了),停頓時間徹底能夠控制在幾十毫秒最多一百多毫秒之內,只要不是頻繁發生,這點停頓是能夠接受的。因此,Serial收集器對於運行在來講是一個很好的選擇。

並行收集器(ParNew)

  ParNew收集器是JAVA虛擬機中垃圾收集器的一種。它是Serial收集器的多線程版本,除了使用多條線程進行垃圾收集以外,其他行爲包括Serial收集器可用的全部控制參數(例如:-XX:SurvivorRatio、 -XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、對象分配規則、回收策略等都與Serial收集器一致。

  ParNew是許多運行在Server模式下的虛擬機中首選的新生代收集器,,除了Serial收集器外,只有它能與CMS收集器配合工做。

Paraller Scavenge收集器  

  Parallel是採用複製算法的多線程新生代垃圾回收器,彷佛和ParNew收集器有不少的類似的地方。可是Parallel Scanvenge收集器的一個特色是它所關注的目標是吞吐量(Throughput)。所謂吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量=運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間)。停頓時間越短就越適合須要與用戶交互的程序,良好的響應速度可以提高用戶的體驗;而高吞吐量則能夠最高效率地利用CPU時間,儘快地完成程序的運算任務,主要適合在後臺運算而不須要太多交互的任務。

  Parallel Old收集器是Parallel Scavenge收集器的老年代版本,採用多線程和」標記-整理」算法。這個收集器是在jdk1.6中才開始提供的,在此以前,新生代的Parallel Scavenge收集器一直處於比較尷尬的狀態。緣由是若是新生代Parallel Scavenge收集器,那麼老年代除了Serial Old(PS MarkSweep)收集器外別無選擇。因爲單線程的老年代Serial Old收集器在服務端應用性能上的」拖累「,即便使用了Parallel Scavenge收集器也未必能在總體應用上得到吞吐量最大化的效果,又由於老年代收集中沒法充分利用服務器多CPU的處理能力,在老年代很大並且硬件比較高級的環境中,這種組合的吞吐量甚至還不必定有ParNew加CMS的組合」給力「。直到Parallel Old收集器出現後,」吞吐量優先「收集器終於有了比較名副其實的應用祝賀,在注重吞吐量及CPU資源敏感的場合,均可以優先考慮Parallel Scavenge加Parallel Old收集器。

  -UseParallelGC: 虛擬機運行在Server模式下的默認值,打開此開關後,使用Parallel Scavenge + Serial Old的收集器組合進行內存回收。-UseParallelOldGC: 打開此開關後,使用Parallel Scavenge + Parallel Old的收集器組合進行垃圾回收

 

老年代收集器

Serial Old收集器

  Serial Old是Serial收集器的老年代版本,它一樣是一個單線程收集器,使用「標記-整理」算法。

Parallel Old收集器

  Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和「標記-整理」算法。

CMS收集器  

  CMS收集器是基於「標記-清除」算法實現的,它的運做過程相對於前面幾種收集器來講要更復雜一些,整個過程分爲6個步驟,包括:     

    初始標記(CMS initial mark)

    併發標記(CMS concurrent mark)

    併發預清理(CMS-concurrent-preclean)

    從新標記(CMS remark)

    併發清除(CMS concurrent sweep)

    併發重置(CMS-concurrent-reset)

  其中初始標記、從新標記這兩個步驟仍然須要「Stop The World」。初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快,併發標記階段就是進行GC Roots Tracing的過程,而從新標記階段則是爲了修正併發標記期間,因用戶程序繼續運做而致使標記產生變更的那一部分對象的標記記錄,這個階段的停頓時間通常會比初始標記階段稍長一些,但遠比並發標記的時間短。其餘動做都是併發的。

 

分區收集-G1 收集器

 

JVM優化

JVM小工具

  java jdk的bin目錄下帶有一些jvm小工具。

jps(Java Virtual Machine Process Status Tool)

  列出正在運行的java進程,並顯示執行主類的名稱及進程在本地JVM中的ID。

  使用方法:

    jps [options][hostid] [options]:

  -q: 只輸出LVMID

  -m: 輸出JVM啓動時傳給主類的方法

  -l:輸出主類的全名,若是是Jar則輸出jar的路徑

  -v: 輸出JVM啓動參數

jstat(Java Virtual Machine Statistics Monitoring Tool)

  JVM統計信息監控工具.

  監控JVM各類運行狀態信息,如虛擬機進程中的類裝載、內存、GC、JIT編譯等數據。

  使用方法:

  vmid是虛擬機ID,在Linux/Unix系統上通常就是進程ID。interval是採樣時間間隔。count是採樣數目。

  S0C、S1C、S0U、S1U:Surivor 0/1區容量(Capacity)和使用量(Used)

  EC、EU:Eden區容量和使用量

  OC、OU:年老代容量和使用量

  MC、MU:方法區容量和使用量

  CCSC、CCSU:壓縮類容量和使用量

  YGC、YGT:年輕代GC次數和GC耗時

  FGC、FGT:Full GC次數和Full GC耗時

  GCT:GC耗時

jmap(Java Virtual Machine Memory Map for Java)

  java內存映像工具。

  用於生成堆轉儲快照,即dump文件能夠查詢finalize執行隊列、Java堆和永久代的詳細信息(使用率、當前用的GC等)。

jstack(Java Virtual Machine Stack Trace for Java)

  堆棧跟蹤工具。

  用於生成JVM當前的線程快照(即當前JVM內每個條線程正在執行的方法堆棧集合)用於分析線程出現長時間停頓的緣由。

javap

  查看經javac以後產生的JVM字節碼代碼

jcmd

  一個多功能工具,能夠用來導出堆,查看Java進程、導出線程信息、執行GC、查看性能相關數據等。

jvisualvm

  JDK中最強大運行監視和故障處理工具

 

JVM參數介紹

  -Xms:初始堆大小

  -Xmx:最大堆大小

  -XX:NewSize=n 設置年輕代大小

  -XX:NewRatio=n 設置年輕代和老年代的比值

  -XX:SurvivorRatio=n 年輕代中Eden區與兩個Survivor區的比值

  -XX:MaxPermSize=n 設置持久代大小

 

  收集器設置

  -XX:+UseSerialGC 設置串行收集器

  -XX:+UseParallelGC 設置並行收集器

  -XX:+UseParalledlOldGC 設置並行年老代收集器

  -XX:+UseConcMarkSweepGC 設置併發收集器

 

  垃圾回收統計信息

  -XX:+PrintGC

  -XX:+Printetails

  -XX:PrintGCTimeStamps

  -Xloggc:filename

相關文章
相關標籤/搜索