Java的GC機制
回收的對象:不存在任何引用的對象
堆區(qū)(Heap)
堆區(qū)是GC最頻繁的,也是理解GC機制最重要的區(qū)域笼痹。堆區(qū)由所有線程共享配喳,在虛擬機啟動時創(chuàng)建。堆區(qū)主要用于存放對象實例及數(shù)組凳干,所有new出來的對象都存儲在該區(qū)域界逛。
如何判斷對象是垃圾 ?
引用計數(shù)算法
經典的引用計數(shù)算法,每個對象添加到引用計數(shù)器纺座,每被引用一次,計數(shù)器+1溉潭,失去引用净响,計數(shù)器-1,當計數(shù)器在一段時間內為0時喳瓣,即認為該對象可以被回收了馋贤。但是這個算法有個明顯的缺陷:當兩個對象相互引用,但是二者都已經沒有作用時畏陕,理應把它們都回收配乓,但是由于它們相互引用,不符合垃圾回收的條件,所以就導致無法處理掉這一塊內存區(qū)域犹芹。因此崎页,Sun的JVM并沒有采用這種算法,而是采用一個叫——根搜索算法腰埂,如圖:
根搜索算法
基本思想是:從一個叫GC Roots的根節(jié)點出發(fā)飒焦,向下搜索,如果一個對象不能達到GC Roots的時候屿笼,說明該對象不再被引用牺荠,可以被回收。如上圖中的Object5驴一、Object6休雌、Object7,雖然它們三個依然相互引用肝断,但是它們其實已經沒有作用了杈曲,這樣就解決了引用計數(shù)算法的缺陷。
內存分代
JVM區(qū)域總體分兩類孝情,heap區(qū)和非heap區(qū)鱼蝉。
heap區(qū)又分為:
Eden Space
(伊甸園)、
對象被創(chuàng)建的時候首先放到這個區(qū)域箫荡,進行垃圾回收后魁亦,不能被回收的對象被放入到空的survivor區(qū)域
Survivor Space
(幸存者區(qū))、
用于保存在eden space內存區(qū)域中經過垃圾回收后沒有被回收的對象
回收的時候Eden區(qū)域不能被回收的對象被放入到空的survivor(也就是To Survivor羔挡,同時Eden區(qū)域的內存會在垃圾回收的過程中全部釋放)洁奈,
另一個survivor(即From Survivor)里不能被回收的對象也會被放入這個survivor(即To Survivor),然后To Survivor 和 From Survivor的標記會互換
绞灼,始終保證一個survivor是空的
利术,涉及到一個算法
Eden Space
和Survivor Space
都屬于新生代,新生代中執(zhí)行的垃圾回收被稱之為Minor GC(因為是對新生代進行垃圾回收低矮,所以又被稱為Young GC)印叁,每一次Young GC后留下來的對象age加1
新生代進行垃圾回收時會出發(fā)
Minor GC
(也稱作Young GC
)
Old Gen
(老年代)。
非heap區(qū)又分:
Code Cache
(代碼緩存區(qū))军掂;
Perm Gen
(永久代)轮蜕;
(Perm Gen全稱是Permanent Generation space,是指內存的永久保存區(qū)域)
Jvm Stack
(java虛擬機棧)蝗锥;
默認大小為物理內存的1/64跃洛。
不會被JVM垃圾回收
老年代用于存放新生代多次回收依然存活的對象,如緩存對象终议。老年代滿了的時候就需要對老年代進行回收汇竭,老年代的垃圾回收稱作
Major GC
(也稱作Full GC
)
永久代是有大小限制的葱蝗,因此如果加載的類太多,很有可能導致永久代內存溢出
Local Method Statck
(本地方法棧)细燎;
垃圾回收器
1.串行收集
2.并行收集
3.CMS收集器
4.G1收集器
使用G1收集器時两曼,Java堆的內存布局與其他收集器有很大差別,它將整個Java堆劃分為多個大小相等的獨立區(qū)域(Region)找颓,雖然還保留有新生代和老年代的概念合愈,但新生代和老年代不再是物理隔閡了,它們都是一部分(可以不連續(xù))Region的集合击狮。
GC常見算法佛析?
1.復制算法
2.標記--清除算法
3.標記--壓縮算法
4.分代回收算法
JVM參數(shù)調優(yōu)
注:jdk8 開始,用 MetaSpace (元空間)區(qū)取代了 Perm 區(qū)(永久代)彪蓬,所以相應的 jvm 參數(shù)變成 -XX:MetaspaceSize 及 -XX:MaxMetaspaceSize寸莫。
MetaSpace GC
如果Metaspace的空間占用達到了設定的最大值,那么就會觸發(fā)GC來收集死亡對象和類的加載器档冬。根據(jù)JDK 8的特性膘茎,G1和CMS都會很好地收集Metaspace區(qū)(一般都伴隨著Full GC)。
為了減少垃圾回收的頻率及時間酷誓,控制吞吐量披坏,對Metaspace進行適當?shù)谋O(jiān)控和調優(yōu)是非常有必要的。如果在Metaspace區(qū)發(fā)生了頻繁的Full GC盐数,那么可能表示存在內存泄露或Metaspace區(qū)的空間太小了棒拂。
新增的 JVM 參數(shù)
-XX:MetaspaceSize 是分配給類元數(shù)據(jù)空間(以字節(jié)計)的初始大小(Oracle邏輯存儲上的初始高水位,the initial high-water-mark )玫氢,此值為估計值帚屉。MetaspaceSize的值設置的過大會延長垃圾回收時間。垃圾回收過后漾峡,引起下一次垃圾回收的類元數(shù)據(jù)空間的大小可能會變大攻旦。
-XX:MaxMetaspaceSize 是分配給類元數(shù)據(jù)空間的最大值,超過此值就會觸發(fā)Full GC生逸,此值默認沒有限制牢屋,但應取決于系統(tǒng)內存的大小。JVM會動態(tài)地改變此值槽袄。
-XX:MinMetaspaceFreeRatio 表示一次GC以后伟阔,為了避免增加元數(shù)據(jù)空間的大小,空閑的類元數(shù)據(jù)的容量的最小比例掰伸,不夠就會導致垃圾回收。
-XX:MaxMetaspaceFreeRatio 表示一次GC以后怀估,為了避免增加元數(shù)據(jù)空間的大小狮鸭,空閑的類元數(shù)據(jù)的容量的最大比例合搅,不夠就會導致垃圾回收。
JVM參數(shù)優(yōu)化
調優(yōu)原則
調優(yōu)方法以及原則一切都是為了這一步歧蕉,調優(yōu)灾部,在調優(yōu)之前,我們需要記住下面的原則:
1惯退、多數(shù)的Java應用不需要在服務器上進行GC優(yōu)化赌髓;
2、多數(shù)導致GC問題的Java應用催跪,都不是因為我們參數(shù)設置錯誤锁蠕,而是代碼問題;
3懊蒸、在應用上線之前荣倾,先考慮將機器的JVM參數(shù)設置到最優(yōu)(最適合);
4骑丸、減少創(chuàng)建對象的數(shù)量舌仍;
5、減少使用全局變量和大對象通危;
6铸豁、GC優(yōu)化是到最后不得已才采用的手段;
7菊碟、在實際使用中节芥,分析GC情況優(yōu)化代碼比優(yōu)化GC參數(shù)要多得多;
Java虛擬機參數(shù)設置:
(1)性能參數(shù):
-server
以server模式運行時將擁有:更大框沟、更高的并發(fā)處理能力藏古,更快更強捷的JVM垃圾回收機制,可以獲得更多的負載與吞吐量
-Xmx
指定java程序的最大堆內存, 使用java -Xmx5000M -version判斷當前系統(tǒng)能分配的最大堆內存
-Xms
指定最小堆內存, 通常設置成跟最大堆內存一樣忍燥,減少GC
-Xmn
設置年輕代大小為512m拧晕。整個堆大小=年輕代大小 + 年老代大小。所以增大年輕代后梅垄,將會減小年老代大小厂捞。此值對系統(tǒng)性能影響較大,Sun官方推薦配置為整個堆的3/8队丝。
-Xss
指定線程的最大椕夷伲空間, 此參數(shù)決定了java函數(shù)調用的深度, 值越大調用深度越深, 若值太小則容易出棧溢出錯誤(StackOverflowError)
設定每個線程的堆棧大小。這個就要依據(jù)你的程序机久,看一個線程 大約需要占用多少內存臭墨,可能會有多少線程同時運行等。一般不易設置超過1M膘盖,要不然容易出現(xiàn)out ofmemory
-XX:PermSize
指定方法區(qū)(永久區(qū))的初始值,默認是物理內存的1/64胧弛, 在Java8永久區(qū)移除, 代之的是元數(shù)據(jù)區(qū)尤误, 由-XX:MetaspaceSize指定
-XX:MaxPermSize
指定方法區(qū)的最大值, 默認是物理內存的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,因為Survivor區(qū)有兩個(from, to)
-XX:+DoEscapeAnalysis
開啟逃逸分析, 逃逸分析的目的是判斷對象的作用域是否可能逃逸出函數(shù)體, 逃逸分析是棧上分配的技術基礎红竭,對于非逃逸對象而言就是一個局部變量, 而對象未發(fā)生逃逸時, 虛擬機就有可能進行線上分配, 不是堆上, 棧上分配速度快尤勋,并且能避免垃圾回收帶來的負面影響, 棧上分配是虛擬機提供的很好的對象分配優(yōu)化策略
-XX:+EliminateAllocations
開啟標量替換(默認打開), 即允許對象打散分配在棧上, 即對象的屬性視為獨立局部變量進行分配到棧上
-XX:+UseCompressedOops
開啟指針壓縮
-XX:+AggressiveOpts
啟用JVM開發(fā)團隊最新的調優(yōu)成果。例如編譯優(yōu)化茵宪,偏向鎖最冰,并行年老代收集等
-XX:-UseTLAB
關閉TLAB , 默認是打開的
-Djava.awt.headless=true
有時我們會在我們的J2EE工程中使用一些圖表工具如:jfreechart,用于在web網頁輸出GIF/JPG等流眉厨,在winodws環(huán)境下锌奴,一般我 們的app server在輸出圖形時不會碰到什么問題,但是在linux/unix環(huán)境下經常會碰到一個exception導致你在winodws開發(fā)環(huán)境下圖片顯 示的好好可是在linux/unix下卻顯示不出來憾股,因此加上這個參數(shù)以免避這樣的情況出現(xiàn).
-XX:+DisableExplicitGC
在程序代碼中不允許有顯示的調用”System.gc()”鹿蜀。看到過有兩個極品工程中每次在DAO操作結束時手動調用System.gc()一下服球,覺得這 樣做好像能夠解決它們的out ofmemory問題一樣罕拂,付出的代價就是系統(tǒng)響應時間嚴重降低枫笛,就和我在關于Xms,Xmx里的解釋的原理一樣民宿,這樣去調用GC導致系統(tǒng)的JVM大起大 落进胯,性能不到什么地方去喲!
-XX:+UseBiasedLocking
偏向鎖粉渠,啟用一個優(yōu)化了的線程鎖分冈,我們知道在我們的appserver,每個http請求就是一個線程霸株,有的請求短有的請求長雕沉,就會有請求排隊的現(xiàn)象,甚至還會出現(xiàn)線程阻塞去件,這個優(yōu)化了的線程鎖使得你的appserver內對線程處理自動進行最優(yōu)調配坡椒。
GC策略
策略 1:將新對象預留在新生代,由于 Full GC 的成本遠高于 Minor GC尤溜,因此盡可能將對象分配在新生代是明智的做法倔叼,實際項目中根據(jù) GC 日志分析新生代空間大小分配是否合理,適當通過“-Xmn”命令調節(jié)新生代大小宫莱,最大限度降低新對象直接進入老年代的情況丈攒。
策略 2:大對象進入老年代,雖然大部分情況下,將對象分配在新生代是合理的巡验。但是對于大對象這種做法卻值得商榷识椰,大對象如果首次在新生代分配可能會出現(xiàn)空間不足導致很多年齡不夠的小對象被分配的老年代,破壞新生代的對象結構深碱,可能會出現(xiàn)頻繁的 full gc。因此藏畅,對于大對象敷硅,可以設置直接進入老年代(當然短命的大對象對于垃圾回收老說簡直就是噩夢)。-XX:PretenureSizeThreshold 可以設置直接進入老年代的對象大小愉阎。
策略 3:合理設置進入老年代對象的年齡绞蹦,-XX:MaxTenuringThreshold 設置對象進入老年代的年齡大小,減少老年代的內存占用榜旦,降低 full gc 發(fā)生的頻率幽七。
策略 4:設置穩(wěn)定的堆大小,堆大小設置有兩個參數(shù):-Xms 初始化堆大小溅呢,-Xmx 最大堆大小澡屡。
策略5:注意:如果滿足下面的指標,則一般不需要進行 GC 優(yōu)化:
MinorGC 執(zhí)行時間不到50ms咐旧;
Minor GC 執(zhí)行不頻繁驶鹉,約10秒一次;
Full GC 執(zhí)行時間不到1s铣墨;
Full GC 執(zhí)行頻率不算頻繁室埋,不低于10分鐘1次。
監(jiān)控JVM
一伊约,需求:大數(shù)據(jù)的機器姚淆,很多jvm進程,需要監(jiān)控特定的幾個的進程的FGC,FGCT,YGC,YGCT,GCT屡律。
YGC:從應用程序啟動到采樣時年輕代中gc次數(shù)
YGCT:從應用程序啟動到采樣時年輕代中gc所用時間(s)
FGC:從應用程序啟動到采樣時old代(全gc)gc次數(shù)
FGCT:從應用程序啟動到采樣時old代(全gc)gc所用時間(s)
GCT:從應用程序啟動到采樣時gc用的總時間(s)
二 編寫自定義監(jiān)控項腳步
jps命令可以獲取到進程的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
四上忍,其它地方的調整
在本機測試腳本是正常的,但是在zabbix server上測試有幾個問題需要修改
編輯 /etc/sudoers文件纳本,添加一行:
zabbix ALL=(ALL) NOPASSWD:ALL
這一行需要注釋掉:#Defaults requiretty
五窍蓝,創(chuàng)建監(jiān)控項(數(shù)據(jù)類型選擇浮點數(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.reibang.com/p/bc2e4d4ff018