GC算法
標記整理
標記清除
缺點:和標記整理比,就是沒有壓縮的操作,會產生內存碎片,所以在後面的垃圾收集器大多都是使用標記整理
複製算法
分代收集算法
準確來講,跟前面三種算法有所區別。分代收集算法就是根據對象的年代,採用上述三種算法來收集。
- 對於新生代:每次GC都有大量對象死去,存活的很少,常採用複製算法,只需要拷貝很少的對象。
- 對於老年代:常採用標整或者標清算法。
四種垃圾收集器
Java 8可以將垃圾收集器分爲四類。
串行收集器Serial
爲單線程環境設計且只使用一個線程進行GC,會暫停所有用戶線程,不適用於服務器。就像去餐廳吃飯,只有一個清潔工在打掃。
並行收集器Parrallel
使用多個線程並行地進行GC,會暫停所有用戶線程,適用於科學計算、大數據後臺,交互性不敏感的場合。多個清潔工同時在打掃。
併發收集器CMS
用戶線程和GC線程同時執行(不一定是並行,交替執行),GC時不需要停頓用戶線程,互聯網公司多用,適用對響應時間有要求的場合。清潔工打掃的時候,也可以就餐。
G1收集器
對內存的劃分與前面3種很大不同,將堆內存分割成不同的區域,然後併發地進行垃圾回收。
默認垃圾收集器
默認收集器有哪些?
有Serial
、Parallel
、ConcMarkSweep
(CMS)、ParNew
、ParallelOld
、G1
。還有一個SerialOld
,快被淘汰了。
查看默認垃圾修改器
使用java -XX:+PrintCommandLineFlags
即可看到,Java 8默認使用-XX:+UseParallelGC
。
七大垃圾收集器
體系結構
Serial
、Parallel Scavenge
、ParNew
用戶回收新生代;SerialOld
、ParallelOld
、CMS
用於回收老年代。而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,從而造成較大停頓時間
過程
- 初始標記:只是標記一下GC Roots能直接關聯的對象,速度很快,需要STW。
- 併發標記:主要標記過程,標記全部對象,和用戶線程一起工作,不需要STW。
- 重新標記:修正在併發標記階段出現的變動,需要STW。
- 併發清除:和用戶線程一起,清除垃圾,不需要STW。
優缺點
優點:停頓時間少,響應速度快,用戶體驗好。
缺點:
- 對CPU資源非常敏感:由於需要併發工作,多少會佔用系統線程資源。
- 無法處理浮動垃圾:由於標記垃圾的時候,用戶進程仍然在運行,無法有效處理新產生的垃圾。
- 產生內存碎片:由於使用標清算法,會產生內存碎片。
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
等參數可調。
特點
- 並行和併發:充分利用多核、多線程CPU,儘量縮短STW。
- 分代收集:雖然還保留着新、老兩代的概念,但物理上不再隔離,而是融合在Region中。
- 空間整合:
G1
整體上看是標整算法,在局部看又是複製算法,不會產生內存碎片。 - 可預測停頓:用戶可以指定一個GC停頓時間,
G1
收集器會盡量滿足。
過程
與CMS
類似。
- 初始標記。
- 併發標記。
- 最終標記。
- 篩選回收。
這些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 average
。load average
後面的三個數字,表示系統1分鐘、5分鐘、15分鐘的平均負載值。如果三者平均值高於0.6,則複雜比較高了。當然,用uptime
也可以查看。
vmstat
查看進程、內存、I/O等多個系統運行狀態。2表示每兩秒採樣一次,3表示一共採樣3次。procs
的r
表示運行和等待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.Throwable
→java.lang.Error
→java.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
元空間滿了就會拋出這個異常。