JVM的算法和垃圾收集器

GC算法

標記整理

標記清除


缺點:和標記整理比,就是沒有壓縮的操作,會產生內存碎片,所以在後面的垃圾收集器大多都是使用標記整理

複製算法

分代收集算法

準確來講,跟前面三種算法有所區別。分代收集算法就是根據對象的年代,採用上述三種算法來收集。

  1. 對於新生代:每次GC都有大量對象死去,存活的很少,常採用複製算法,只需要拷貝很少的對象。
  2. 對於老年代:常採用標整或者標清算法。

四種垃圾收集器

Java 8可以將垃圾收集器分爲四類。

串行收集器Serial

爲單線程環境設計且只使用一個線程進行GC,會暫停所有用戶線程,不適用於服務器。就像去餐廳吃飯,只有一個清潔工在打掃。

並行收集器Parrallel

使用多個線程並行地進行GC,會暫停所有用戶線程,適用於科學計算、大數據後臺,交互性不敏感的場合。多個清潔工同時在打掃。

併發收集器CMS

用戶線程和GC線程同時執行(不一定是並行,交替執行),GC時不需要停頓用戶線程,互聯網公司多用,適用對響應時間有要求的場合。清潔工打掃的時候,也可以就餐。

G1收集器

對內存的劃分與前面3種很大不同,將堆內存分割成不同的區域,然後併發地進行垃圾回收。

默認垃圾收集器

默認收集器有哪些?

SerialParallelConcMarkSweep(CMS)、ParNewParallelOldG1。還有一個SerialOld,快被淘汰了。
在這裏插入圖片描述
在這裏插入圖片描述

查看默認垃圾修改器

使用java -XX:+PrintCommandLineFlags即可看到,Java 8默認使用-XX:+UseParallelGC
在這裏插入圖片描述

七大垃圾收集器

體系結構

SerialParallel ScavengeParNew用戶回收新生代;SerialOldParallelOldCMS用於回收老年代。而G1收集器,既可以回收新生代,也可以回收老年代。
在這裏插入圖片描述
連線表示可以搭配使用,紅叉表示不推薦一同使用,比如新生代用Serial,老年代用CMS

Serial收集器

年代最久遠,是Client VM模式下的默認新生代收集器,新生代使用複製算法,老年代使用標記整理算法
優點:單個線程收集,沒有線程切換開銷,擁有最高的單線程GC效率。
缺點:收集的時候會暫停用戶線程。

使用-XX:+UseSerialGC可以顯式開啓,開啓後默認使用Serial+SerialOld的組合。

ParNew收集器

也就是Serial的多線程版本,GC的時候不再是一個線程,而是多個,是Server VM模式下的默認新生代收集器,採用複製算法。老年代使用標記整理算法

使用-XX:+UseParNewGC可以顯式開啓,開啓後默認使用ParNew+SerialOld的組合。但是由於SerialOld已經過時,所以建議配合CMS使用。
在這裏插入圖片描述

Parallel Scavenge收集器

ParNew收集器僅在新生代使用多線程收集,老年代默認是SerialOld,所以是單線程收集。而Parallel Scavenge在新、老兩代都採用多線程收集。Parallel Scavenge還有一個特點就是吞吐量優先收集器,可以通過自適應調節,保證最大吞吐量。採用複製算法

使用-XX:+UseParallelGC可以開啓, 同時也會使用ParallelOld收集老年代。其它參數,比如-XX:ParallelGCThreads=N可以選擇N個線程進行GC,-XX:+UseAdaptiveSizePolicy使用自適應調節策略。

SerialOld收集器

Serial的老年代版本,採用標整算法。JDK1.5之前跟Parallel Scavenge配合使用,現在已經不了,作爲CMS的後備收集器。

ParallelOld收集器

Parallel的老年代版本,JDK1.6之前,新生代用Parallel而老年代用SerialOld,只能保證新生代的吞吐量。JDK1.8後,老年代改用ParallelOld

使用-XX:+UseParallelOldGC可以開啓, 同時也會使用Parallel收集新生代。

CMS收集器

併發標記清除收集器,是一種以獲得最短GC停頓爲目標的收集器。適用在互聯網或者B/S系統的服務器上,這類應用尤其重視服務器的響應速度,希望停頓時間最短。是G1收集器出來之前的首選收集器。使用標清算法。在GC的時候,會與用戶線程併發執行,不會停頓用戶線程。但是在標記的時候,仍然會STW

使用-XX:+UseConcMarkSweepGC開啓。開啓過後,新生代默認使用ParNew,同時老年代使用SerialOld作爲備用。
由於併發進行,CMS在收集與應用線程會同時會增加對堆內存的佔用,也就是說,CMS必須要在老年代準內存用盡之前完成垃圾回收,否則CMS回收失敗時,將觸發擔保機制,串行老年代收集器將會以TW的方式進行一次GC,從而造成較大停頓時間

過程

  1. 初始標記:只是標記一下GC Roots能直接關聯的對象,速度很快,需要STW
  2. 併發標記:主要標記過程,標記全部對象,和用戶線程一起工作,不需要STW。
  3. 重新標記:修正在併發標記階段出現的變動,需要STW
  4. 併發清除:和用戶線程一起,清除垃圾,不需要STW。

優缺點

優點:停頓時間少,響應速度快,用戶體驗好。

缺點

  1. 對CPU資源非常敏感:由於需要併發工作,多少會佔用系統線程資源。
  2. 無法處理浮動垃圾:由於標記垃圾的時候,用戶進程仍然在運行,無法有效處理新產生的垃圾。
  3. 產生內存碎片:由於使用標清算法,會產生內存碎片。

G1收集器

G1收集器與之前垃圾收集器的一個顯著區別就是——之前收集器都有三個區域,新、老兩代和元空間。而G1收集器只有G1區和元空間。而G1區,不像之前的收集器,分爲新、老兩代,而是一個一個Region,每個Region既可能包含新生代,也可能包含老年代。

G1收集器既可以提高吞吐量,又可以減少GC時間。最重要的是STW可控,增加了預測機制,讓用戶指定停頓時間。

區域化內存劃片Region,整體編爲了一些列不連續的內存區域,避免了全內存區的GC操作。

核心思想是將整個堆內存區域分成大小相同的子區域(Region),在JVM啓動時會自動設置這些子區域的大小,

在堆的使用上,G1並不要求對象的存儲一定是物理上連續的只要邏輯上連續即可,每個分區也不會固定地爲某個代服務,可以按需在年輕代和老年代之間切換。啓動時可以通過參數-XX:G1HeapRegionSize=n可指定分區大小(1MB~32MB,且必 須是2的冪),默認將整堆劃分爲2048個分區。.
大小範圍在1MB~32MB,最多能設置2048個區域,也即能夠支持的最大內存爲:32MB*2048=65536MB=64G的存

使用-XX:+UseG1GC開啓,還有-XX:G1HeapRegionSize=n-XX:MaxGCPauseMillis=n等參數可調。

特點

  1. 並行和併發:充分利用多核、多線程CPU,儘量縮短STW。
  2. 分代收集:雖然還保留着新、老兩代的概念,但物理上不再隔離,而是融合在Region中。
  3. 空間整合G1整體上看是標整算法,在局部看又是複製算法,不會產生內存碎片。
  4. 可預測停頓:用戶可以指定一個GC停頓時間,G1收集器會盡量滿足。

過程

CMS類似。

  1. 初始標記。
  2. 併發標記。
  3. 最終標記。
  4. 篩選回收。
    在這裏插入圖片描述

這些Region的一- 部分包含新生代,新生代的垃圾收集依然採用暫停所有應用線程的方式,將存活對象拷貝到老年代或者Survivor空間。

這些Region的一部分包含 老年代,G1收集器通過將對象從-一個區域複製到另外一個區域,完成了清理工作。這就意味着,在正常的處理過程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有CMS內存碎片問題的存在了

在G1中,還有一種特殊的區域,叫Humongous(巨大的)區域

如果一個對象佔用的空間超過了分區容量50%以上,G1收集器就認爲這是一個巨型對象。這些巨型對象默認直接會被分配在年老代,但是如果.它是-個短期存在的巨型對象,就會對垃圾收集器造成負面影響。爲了解決這個問題,G1劃分了一個Humongous區,它用來專[存放巨型對象。如果一個H區裝不下-一個巨型對象,那麼G1會尋找連續的H分區來存儲。爲了能找到連續的H區,有時候不得不啓動Full GC。
在這裏插入圖片描述
針對Eden區進行收集,Eden區耗盡後會被觸發,主要是小區域收集+形成連續的內存塊,避免內存碎片*Eden區的數據移動到Survivor區,假如出現Survivor區空間不夠,Eden區數據不會晉升到Old區
Survivor區的數據移動到新的Survivor區,部會數據晉升到Old區最後Eden區收拾乾淨了,GC結束, 用戶的應用程序繼續執行

在這裏插入圖片描述

附—Linux相關指令

top

主要查看%CPU%MEM,還有load averageload average後面的三個數字,表示系統1分鐘、5分鐘、15分鐘的平均負載值。如果三者平均值高於0.6,則複雜比較高了。當然,用uptime也可以查看。

vmstat

查看進程、內存、I/O等多個系統運行狀態。2表示每兩秒採樣一次,3表示一共採樣3次。procsr表示運行和等待CPU時間片的進程數,原則上1核CPU不要超過2。b是等待資源的進程數,比如磁盤I/O、網絡I/O等。

[root@ ~]# vmstat -n 2 3
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0      0 173188 239748 1362628    0    0     0     3   17    8  0  0 99  0  0
 0  0      0 172800 239748 1362636    0    0     0     0  194  485  1  1 99  0  0
 1  0      0 172800 239748 1362640    0    0     0     0  192  421  1  1 99  0  0

pidstat

查看某個進程的運行信息。
在這裏插入圖片描述

free

查看內存信息。
在這裏插入圖片描述

df

查看磁盤信息。

iostat

查看磁盤I/O信息。比如有時候MySQL在查表的時候,會佔用大量磁盤I/O,體現在該指令的%util字段很大。對於死循環的程序,CPU佔用固然很高,但是磁盤I/O不高。
在這裏插入圖片描述

ifstat

查看網絡I/O信息,需要安裝。
在這裏插入圖片描述

CPU佔用過高原因定位

先用top找到CPU佔用最高的進程,然後用ps -mp pid -o THREAD,tid,time,得到該進程裏面佔用最高的線程。這個線程是10進制的,將其轉成16進制,然後用jstack pid | grep tid可以定位到具體哪一行導致了佔用過高。

JVM性能調優和監控工具

jps

Java版的ps -ef查看所有JVM進程。

jstack

查看JVM中運行線程的狀態,比較重要。可以定位CPU佔用過高位置,定位死鎖位置。

jinfo/jstat

jinfo查看JVM的運行環境參數,比如默認的JVM參數等。jstat統計信息監視工具。

jmap

JVM內存映像工具。

OutOfMemoryError

在這裏插入圖片描述

StackOverflowError

棧滿會拋出該錯誤。無限遞歸就會導致StackOverflowError,
java.lang.Throwablejava.lang.Errorjava.lang.VirtualMachineError下的錯誤。一般在遞歸中容易出現這種問題,當我們調用方法時就會在棧中開闢一個新的空間,

OOM—Java head space

堆滿會拋出該錯誤。

 public static void main(String[] args) {
        String str = "adf";
        while (true) {
            str += str + new Random().nextInt(1111111) + new Random().nextInt(222222);
            str.intern();
        }
    }

在這裏插入圖片描述

OOM—GC overhead limit exceeded

這個錯誤是指:GC的時候會有“Stop the World",STW越小越好,正常情況是GC只會佔到很少一部分時間。但是如果用超過98%的時間來做GC,而且收效甚微,就會被JVM叫停。

OOM—GC Direct buffer memory

在寫NIO程序的時候,會用到ByteBuffer來讀取和存入數據。與Java堆的數據不一樣,ByteBuffer使用native方法,直接在堆外分配內存。當堆外內存(也即本地物理內存)不夠時,就會拋出這個異常

OOM—unable to create new native thread

在高併發應用場景時,如果創建超過了系統默認的最大線程數,就會拋出該異常。Linux單個進程默認不能超過1024個線程。
解決方法
要麼降低程序線程數,
要麼修改系統最大線程數vim /etc/security/limits.d/90-nproc.conf

OOM—Metaspace

元空間滿了就會拋出這個異常。
在這裏插入圖片描述

發佈了106 篇原創文章 · 獲贊 9 · 訪問量 6170
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章