Java的GC機(jī)制
回收的對象:不存在任何引用的對象
堆區(qū)(Heap)
堆區(qū)是GC最頻繁的,也是理解GC機(jī)制最重要的區(qū)域。堆區(qū)由所有線程共享,在虛擬機(jī)啟動時(shí)創(chuàng)建。堆區(qū)主要用于存放對象實(shí)例及數(shù)組,所有new出來的對象都存儲在該區(qū)域。
如何判斷對象是垃圾 ?
引用計(jì)數(shù)算法
經(jīng)典的引用計(jì)數(shù)算法,每個(gè)對象添加到引用計(jì)數(shù)器,每被引用一次,計(jì)數(shù)器+1,失去引用,計(jì)數(shù)器-1,當(dāng)計(jì)數(shù)器在一段時(shí)間內(nèi)為0時(shí),即認(rèn)為該對象可以被回收了。但是這個(gè)算法有個(gè)明顯的缺陷:當(dāng)兩個(gè)對象相互引用,但是二者都已經(jīng)沒有作用時(shí),理應(yīng)把它們都回收,但是由于它們相互引用,不符合垃圾回收的條件,所以就導(dǎo)致無法處理掉這一塊內(nèi)存區(qū)域。因此,Sun的JVM并沒有采用這種算法,而是采用一個(gè)叫——根搜索算法,如圖:

根搜索算法
基本思想是:從一個(gè)叫GC Roots的根節(jié)點(diǎn)出發(fā),向下搜索,如果一個(gè)對象不能達(dá)到GC Roots的時(shí)候,說明該對象不再被引用,可以被回收。如上圖中的Object5、Object6、Object7,雖然它們?nèi)齻€(gè)依然相互引用,但是它們其實(shí)已經(jīng)沒有作用了,這樣就解決了引用計(jì)數(shù)算法的缺陷。
內(nèi)存分代
JVM區(qū)域總體分兩類,heap區(qū)和非heap區(qū)。
heap區(qū)又分為:
Eden Space(伊甸園)、
對象被創(chuàng)建的時(shí)候首先放到這個(gè)區(qū)域,進(jìn)行垃圾回收后,不能被回收的對象被放入到空的survivor區(qū)域
Survivor Space(幸存者區(qū))、
用于保存在eden space內(nèi)存區(qū)域中經(jīng)過垃圾回收后沒有被回收的對象
回收的時(shí)候Eden區(qū)域不能被回收的對象被放入到空的survivor(也就是To Survivor,同時(shí)Eden區(qū)域的內(nèi)存會在垃圾回收的過程中全部釋放),
另一個(gè)survivor(即From Survivor)里不能被回收的對象也會被放入這個(gè)survivor(即To Survivor),然后To Survivor 和 From Survivor的標(biāo)記會互換,始終保證一個(gè)survivor是空的,涉及到一個(gè)算法
Eden Space和Survivor Space都屬于新生代,新生代中執(zhí)行的垃圾回收被稱之為Minor GC(因?yàn)槭菍π律M(jìn)行垃圾回收,所以又被稱為Young GC),每一次Young GC后留下來的對象age加1
新生代進(jìn)行垃圾回收時(shí)會出發(fā)
Minor GC(也稱作Young GC)
Old Gen(老年代)。
非heap區(qū)又分:
Code Cache(代碼緩存區(qū));
Perm Gen(永久代);
(Perm Gen全稱是Permanent Generation space,是指內(nèi)存的永久保存區(qū)域)
Jvm Stack(java虛擬機(jī)棧);
默認(rèn)大小為物理內(nèi)存的1/64。
不會被JVM垃圾回收
老年代用于存放新生代多次回收依然存活的對象,如緩存對象。老年代滿了的時(shí)候就需要對老年代進(jìn)行回收,老年代的垃圾回收稱作
Major GC(也稱作Full GC)
永久代是有大小限制的,因此如果加載的類太多,很有可能導(dǎo)致永久代內(nèi)存溢出
Local Method Statck(本地方法棧);
垃圾回收器
1.串行收集
2.并行收集
3.CMS收集器
4.G1收集器
使用G1收集器時(shí),Java堆的內(nèi)存布局與其他收集器有很大差別,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔閡了,它們都是一部分(可以不連續(xù))Region的集合。
GC常見算法?
1.復(fù)制算法
2.標(biāo)記--清除算法
3.標(biāo)記--壓縮算法
4.分代回收算法
JVM參數(shù)調(diào)優(yōu)

注:jdk8 開始,用 MetaSpace (元空間)區(qū)取代了 Perm 區(qū)(永久代),所以相應(yīng)的 jvm 參數(shù)變成 -XX:MetaspaceSize 及 -XX:MaxMetaspaceSize。
MetaSpace GC
如果Metaspace的空間占用達(dá)到了設(shè)定的最大值,那么就會觸發(fā)GC來收集死亡對象和類的加載器。根據(jù)JDK 8的特性,G1和CMS都會很好地收集Metaspace區(qū)(一般都伴隨著Full GC)。
為了減少垃圾回收的頻率及時(shí)間,控制吞吐量,對Metaspace進(jìn)行適當(dāng)?shù)谋O(jiān)控和調(diào)優(yōu)是非常有必要的。如果在Metaspace區(qū)發(fā)生了頻繁的Full GC,那么可能表示存在內(nèi)存泄露或Metaspace區(qū)的空間太小了。
新增的 JVM 參數(shù)
-XX:MetaspaceSize 是分配給類元數(shù)據(jù)空間(以字節(jié)計(jì))的初始大小(Oracle邏輯存儲上的初始高水位,the initial high-water-mark ),此值為估計(jì)值。MetaspaceSize的值設(shè)置的過大會延長垃圾回收時(shí)間。垃圾回收過后,引起下一次垃圾回收的類元數(shù)據(jù)空間的大小可能會變大。
-XX:MaxMetaspaceSize 是分配給類元數(shù)據(jù)空間的最大值,超過此值就會觸發(fā)Full GC,此值默認(rèn)沒有限制,但應(yīng)取決于系統(tǒng)內(nèi)存的大小。JVM會動態(tài)地改變此值。
-XX:MinMetaspaceFreeRatio 表示一次GC以后,為了避免增加元數(shù)據(jù)空間的大小,空閑的類元數(shù)據(jù)的容量的最小比例,不夠就會導(dǎo)致垃圾回收。
-XX:MaxMetaspaceFreeRatio 表示一次GC以后,為了避免增加元數(shù)據(jù)空間的大小,空閑的類元數(shù)據(jù)的容量的最大比例,不夠就會導(dǎo)致垃圾回收。
JVM參數(shù)優(yōu)化

調(diào)優(yōu)原則
調(diào)優(yōu)方法以及原則一切都是為了這一步,調(diào)優(yōu),在調(diào)優(yōu)之前,我們需要記住下面的原則:
1、多數(shù)的Java應(yīng)用不需要在服務(wù)器上進(jìn)行GC優(yōu)化;
2、多數(shù)導(dǎo)致GC問題的Java應(yīng)用,都不是因?yàn)槲覀儏?shù)設(shè)置錯(cuò)誤,而是代碼問題;
3、在應(yīng)用上線之前,先考慮將機(jī)器的JVM參數(shù)設(shè)置到最優(yōu)(最適合);
4、減少創(chuàng)建對象的數(shù)量;
5、減少使用全局變量和大對象;
6、GC優(yōu)化是到最后不得已才采用的手段;
7、在實(shí)際使用中,分析GC情況優(yōu)化代碼比優(yōu)化GC參數(shù)要多得多;
Java虛擬機(jī)參數(shù)設(shè)置:
(1)性能參數(shù):
-server
以server模式運(yùn)行時(shí)將擁有:更大、更高的并發(fā)處理能力,更快更強(qiáng)捷的JVM垃圾回收機(jī)制,可以獲得更多的負(fù)載與吞吐量
-Xmx
指定java程序的最大堆內(nèi)存, 使用java -Xmx5000M -version判斷當(dāng)前系統(tǒng)能分配的最大堆內(nèi)存
-Xms
指定最小堆內(nèi)存, 通常設(shè)置成跟最大堆內(nèi)存一樣,減少GC
-Xmn
設(shè)置年輕代大小為512m。整個(gè)堆大小=年輕代大小 + 年老代大小。所以增大年輕代后,將會減小年老代大小。此值對系統(tǒng)性能影響較大,Sun官方推薦配置為整個(gè)堆的3/8。
-Xss
指定線程的最大??臻g, 此參數(shù)決定了java函數(shù)調(diào)用的深度, 值越大調(diào)用深度越深, 若值太小則容易出棧溢出錯(cuò)誤(StackOverflowError)
設(shè)定每個(gè)線程的堆棧大小。這個(gè)就要依據(jù)你的程序,看一個(gè)線程 大約需要占用多少內(nèi)存,可能會有多少線程同時(shí)運(yùn)行等。一般不易設(shè)置超過1M,要不然容易出現(xiàn)out ofmemory
-XX:PermSize
指定方法區(qū)(永久區(qū))的初始值,默認(rèn)是物理內(nèi)存的1/64, 在Java8永久區(qū)移除, 代之的是元數(shù)據(jù)區(qū), 由-XX:MetaspaceSize指定
-XX:MaxPermSize
指定方法區(qū)的最大值, 默認(rèn)是物理內(nèi)存的1/4, 在java8中由-XX:MaxMetaspaceSize指定元數(shù)據(jù)區(qū)的大小
-XX:NewRatio=n
年老代與年輕代的比值,-XX:NewRatio=2, 表示年老代與年輕代的比值為2:1
-XX:SurvivorRatio=n
Eden區(qū)與Survivor區(qū)的大小比值,-XX:SurvivorRatio=8表示Eden區(qū)與Survivor區(qū)的大小比值是8:1:1,因?yàn)镾urvivor區(qū)有兩個(gè)(from, to)
-XX:+DoEscapeAnalysis
開啟逃逸分析, 逃逸分析的目的是判斷對象的作用域是否可能逃逸出函數(shù)體, 逃逸分析是棧上分配的技術(shù)基礎(chǔ),對于非逃逸對象而言就是一個(gè)局部變量, 而對象未發(fā)生逃逸時(shí), 虛擬機(jī)就有可能進(jìn)行線上分配, 不是堆上, 棧上分配速度快,并且能避免垃圾回收帶來的負(fù)面影響, 棧上分配是虛擬機(jī)提供的很好的對象分配優(yōu)化策略
-XX:+EliminateAllocations
開啟標(biāo)量替換(默認(rèn)打開), 即允許對象打散分配在棧上, 即對象的屬性視為獨(dú)立局部變量進(jìn)行分配到棧上
-XX:+UseCompressedOops
開啟指針壓縮
-XX:+AggressiveOpts
啟用JVM開發(fā)團(tuán)隊(duì)最新的調(diào)優(yōu)成果。例如編譯優(yōu)化,偏向鎖,并行年老代收集等
-XX:-UseTLAB
關(guān)閉TLAB , 默認(rèn)是打開的
-Djava.awt.headless=true
有時(shí)我們會在我們的J2EE工程中使用一些圖表工具如:jfreechart,用于在web網(wǎng)頁輸出GIF/JPG等流,在winodws環(huán)境下,一般我 們的app server在輸出圖形時(shí)不會碰到什么問題,但是在linux/unix環(huán)境下經(jīng)常會碰到一個(gè)exception導(dǎo)致你在winodws開發(fā)環(huán)境下圖片顯 示的好好可是在linux/unix下卻顯示不出來,因此加上這個(gè)參數(shù)以免避這樣的情況出現(xiàn).
-XX:+DisableExplicitGC
在程序代碼中不允許有顯示的調(diào)用”System.gc()”??吹竭^有兩個(gè)極品工程中每次在DAO操作結(jié)束時(shí)手動調(diào)用System.gc()一下,覺得這 樣做好像能夠解決它們的out ofmemory問題一樣,付出的代價(jià)就是系統(tǒng)響應(yīng)時(shí)間嚴(yán)重降低,就和我在關(guān)于Xms,Xmx里的解釋的原理一樣,這樣去調(diào)用GC導(dǎo)致系統(tǒng)的JVM大起大 落,性能不到什么地方去喲!
-XX:+UseBiasedLocking
偏向鎖,啟用一個(gè)優(yōu)化了的線程鎖,我們知道在我們的appserver,每個(gè)http請求就是一個(gè)線程,有的請求短有的請求長,就會有請求排隊(duì)的現(xiàn)象,甚至還會出現(xiàn)線程阻塞,這個(gè)優(yōu)化了的線程鎖使得你的appserver內(nèi)對線程處理自動進(jìn)行最優(yōu)調(diào)配。
GC策略
策略 1:將新對象預(yù)留在新生代,由于 Full GC 的成本遠(yuǎn)高于 Minor GC,因此盡可能將對象分配在新生代是明智的做法,實(shí)際項(xiàng)目中根據(jù) GC 日志分析新生代空間大小分配是否合理,適當(dāng)通過“-Xmn”命令調(diào)節(jié)新生代大小,最大限度降低新對象直接進(jìn)入老年代的情況。
策略 2:大對象進(jìn)入老年代,雖然大部分情況下,將對象分配在新生代是合理的。但是對于大對象這種做法卻值得商榷,大對象如果首次在新生代分配可能會出現(xiàn)空間不足導(dǎo)致很多年齡不夠的小對象被分配的老年代,破壞新生代的對象結(jié)構(gòu),可能會出現(xiàn)頻繁的 full gc。因此,對于大對象,可以設(shè)置直接進(jìn)入老年代(當(dāng)然短命的大對象對于垃圾回收老說簡直就是噩夢)。-XX:PretenureSizeThreshold 可以設(shè)置直接進(jìn)入老年代的對象大小。
策略 3:合理設(shè)置進(jìn)入老年代對象的年齡,-XX:MaxTenuringThreshold 設(shè)置對象進(jìn)入老年代的年齡大小,減少老年代的內(nèi)存占用,降低 full gc 發(fā)生的頻率。
策略 4:設(shè)置穩(wěn)定的堆大小,堆大小設(shè)置有兩個(gè)參數(shù):-Xms 初始化堆大小,-Xmx 最大堆大小。
策略5:注意:如果滿足下面的指標(biāo),則一般不需要進(jìn)行 GC 優(yōu)化:
MinorGC 執(zhí)行時(shí)間不到50ms;
Minor GC 執(zhí)行不頻繁,約10秒一次;
Full GC 執(zhí)行時(shí)間不到1s;
Full GC 執(zhí)行頻率不算頻繁,不低于10分鐘1次。
監(jiān)控JVM
一,需求:大數(shù)據(jù)的機(jī)器,很多jvm進(jìn)程,需要監(jiān)控特定的幾個(gè)的進(jìn)程的FGC,FGCT,YGC,YGCT,GCT。
YGC:從應(yīng)用程序啟動到采樣時(shí)年輕代中g(shù)c次數(shù)
YGCT:從應(yīng)用程序啟動到采樣時(shí)年輕代中g(shù)c所用時(shí)間(s)
FGC:從應(yīng)用程序啟動到采樣時(shí)old代(全gc)gc次數(shù)
FGCT:從應(yīng)用程序啟動到采樣時(shí)old代(全gc)gc所用時(shí)間(s)
GCT:從應(yīng)用程序啟動到采樣時(shí)gc用的總時(shí)間(s)
二 編寫自定義監(jiān)控項(xiàng)腳步
jps命令可以獲取到進(jìn)程的ID,jstat -gc ID ,獲取gc的信息
參數(shù)詳解
jstat參考
cat jps1.py
#!/usr/bin/env python
import os,sys
jps_info = os.popen("/usr/bin/sudo /usr/local/java/bin/jps | grep %s | awk '{print $1}'"% sys.argv[1])
jps_info = jps_info.read()
jps_id = jps_info.strip()
jstat = {'YGC':'$13','YGCT':'$14','FGC':'$15','FGCT':'$16','GCT':'$17'}
info = jstat[sys.argv[2]]
command = "/usr/bin/sudo /usr/local/java/bin/jstat -gc "+jps_id+" | awk '{print "+info+"}'| /usr/bin/tail -n 1"
os.system(command)
三,編輯zabbix agent配置文件
添加:UserParameter=jstat[*], /usr/bin/python /etc/zabbix/scripts/jps1.py $1 $2
四,其它地方的調(diào)整
在本機(jī)測試腳本是正常的,但是在zabbix server上測試有幾個(gè)問題需要修改
編輯 /etc/sudoers文件,添加一行:
zabbix ALL=(ALL) NOPASSWD:ALL
這一行需要注釋掉:#Defaults requiretty
五,創(chuàng)建監(jiān)控項(xiàng)(數(shù)據(jù)類型選擇浮點(diǎn)數(shù))

六,添加模板展示

如何修改JVM參數(shù)
在安裝目錄的bin/catalina.sh 文件的cygwin=false上面加上:
JAVA_OPTS="-Xms256m -Xmx2048m -XX:PermSize=128m -XX:MaxPermSize=512m"
或者
CATALINA_OPTS="-Xms256m -Xmx2048m -XX:PermSize=128m -XX:MaxPermSize=512m"
參考:
https://www.sczyh30.com/posts/Java/jvm-metaspace/
http://www.itdecent.cn/p/bc2e4d4ff018