聲明:本文基于HotSpot JVM 1.7版本
本文垃圾回收器部分不具體介紹G1
文中部分圖片來源于網(wǎng)絡(luò)汇陆,權(quán)侵刪
1.JVM規(guī)范規(guī)定的運行時數(shù)據(jù)區(qū)域
Java虛擬機規(guī)范將Java內(nèi)存劃分為程序計數(shù)器、Java虛擬機棧、本地方法棧、方法區(qū)镶骗、運行時常量池和直接內(nèi)存,下面我們就簡單介紹一下這些區(qū)域:
1.1 程序計數(shù)器
程序計數(shù)器(Program Counter Register)可以看作是當(dāng)前線程所執(zhí)行字節(jié)碼的行號指示器愉择,字節(jié)碼解釋器工作時就是通過這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令匪凡,分支拔稳、循環(huán)、跳轉(zhuǎn)锹雏、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個程序計數(shù)器來完成术奖。如果線程運行的是一個Java方法礁遵,它記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址;如果運行的是一個Native方法采记,這個計數(shù)器的值則為空(Undefined)佣耐。
1.2 Java虛擬機棧
Java虛擬機棧(Java Vitual Machine Stacks)也是線程私有的,生命周期與線程相同唧龄。虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的同時會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表兼砖、操作數(shù)棧、動態(tài)鏈接既棺、方法出口等信息讽挟。虛擬機棧中的局部變量表存放了編譯器可知的各種基本數(shù)據(jù)類型(boolean、byte丸冕、char耽梅、short、int胖烛、float眼姐、long诅迷、double)、對象引用(可以是指向?qū)ο笃鹗嫉刂返囊弥羔樦谄欤部赡苁侵赶蛞粋€代表對象的句柄或其他與此對象相關(guān)的位置)和returnAdress類型(指向了一條字節(jié)碼指令的地址)罢杉。這部分區(qū)域規(guī)定了兩種異常狀況:如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverFlowError異常贡歧;如果虛擬機椞沧猓可以動態(tài)擴展,如果無法申請到足夠的內(nèi)存艘款,就會拋出OutOfMemoeyError異常持际。
1.3 本地方法棧
本地方法棧(Native Method Stack)與虛擬機棧所發(fā)揮的作用是非常相似的,他們之間的區(qū)別是虛擬機棧執(zhí)行Java方法(也就是字節(jié)碼)服務(wù)哗咆,而本地方法棧則為Native方法服務(wù)蜘欲。與虛擬機棧一樣本地方法棧可能拋出StackOverFlowError和OutOfMemoryError異常晌柬。
1.4 Java堆
Java堆是所有線程共享的一塊區(qū)域姥份,也是Java虛擬機所管理最大的一塊內(nèi)存區(qū)域。此內(nèi)存區(qū)域唯一的目的就是存放對象實例年碘,幾乎所有的對象澈歉、數(shù)組都在這里分配內(nèi)存,但隨著JIT編譯器的發(fā)展與逃逸分析技術(shù)逐漸成熟屿衅,棧上分配埃难、標(biāo)量替換優(yōu)化技術(shù)將會導(dǎo)致一些微妙的變化,所有對象都分配在堆上也漸漸變得不是那么絕對涤久。如果創(chuàng)建對象時無法申請到足夠的內(nèi)存就會拋出OutOfMemoryError異常涡尘。
1.5 方法區(qū)
方法區(qū)(Method Area)也是各個線程共享區(qū)域,它用來存儲已被虛擬機加載的類信息响迂、常量考抄、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)蔗彤。當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時川梅,將拋出OutOfMemoryError異常。
1.6 運行時常量池
運行時常量池(Runtime Constant Pool)是方法區(qū)的一部分然遏。Class文件中除了有類的版本贫途、字段、方法待侵、接口等描述信息外潮饱,還有一項信息是常量池(Constant Pool Table),用于存放編譯器生成的各種字面量和符號引用诫给,這部分內(nèi)容會在類加載后進入方法區(qū)的運行時常量池中存放香拉。作為方法區(qū)的一部分啦扬,當(dāng)常量池?zé)o法申請到內(nèi)存時會拋出OutOfMemoryError異常。
1.7 直接內(nèi)存
直接內(nèi)存(Direct Memory)并不是虛擬機運行時數(shù)據(jù)區(qū)域的一部分凫碌,也不是java虛擬機規(guī)范中定義的內(nèi)存區(qū)域扑毡。在JDK1.4新加入的NIO(New Input/Output)類,引入了一種基于通道(Chnnel)與緩沖區(qū)(Buffer)的I/O方式盛险,他可以使用Native函數(shù)庫直接分配堆外內(nèi)存瞄摊,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內(nèi)存區(qū)域 的引用進行操作。這樣就避免了在Java堆和Native堆中來回復(fù)制數(shù)據(jù)苦掘。雖然直接內(nèi)存不受Java堆大小的限制换帜,但既然是內(nèi)存就會受到本機總內(nèi)存(包括RAM以及SWAP區(qū)或者分頁文件)大小以及處理器尋址空間的限制。
從線程的角度來說鹤啡,Java內(nèi)存可以分為兩類:
所有線程共享區(qū)域
方法區(qū)惯驼、運行時常量池、Java堆递瑰;這些區(qū)域是所有線程共享的祟牲;在Java虛擬機啟動時創(chuàng)建的,只有當(dāng)Java虛擬機退出時才會銷毀抖部。
線程間隔離的數(shù)據(jù)區(qū)
程序計數(shù)器说贝、Java虛擬機棧、本地方法棧慎颗;這些數(shù)據(jù)區(qū)域是每個線程私有的乡恕,不與其他線程共享;每個線程的數(shù)據(jù)區(qū)隨線程創(chuàng)建而創(chuàng)建俯萎,并在線程退出時銷毀傲宜。
2.HotSpot 1.7對JVM規(guī)范中運行時數(shù)據(jù)區(qū)域的實現(xiàn)
2.1 Java虛擬機棧和本地方法棧
HotSpot將Java虛擬機棧和本地方法棧合二為一,統(tǒng)稱為棧內(nèi)存讯屈。
JVM棧內(nèi)存設(shè)置參數(shù):
- -Xss 設(shè)置棧內(nèi)存大小
2.2 堆內(nèi)存和方法區(qū)
HotSpot1.7使用分代回收的思想,將堆內(nèi)存劃分為新生代和老年代县习,而方法區(qū)通過永久代實現(xiàn)涮母。
JVM堆內(nèi)存相關(guān)的參數(shù)如下:
- -Xms 控制Java堆的初始化值
- -Xmx 堆的最大值
- -Xmn 控制年輕代的值
- -Xss 設(shè)置java線程棧的大小
- -XX:NewRatio 年輕代與老年代的比值
- -XX:SurvivorRatio Eden區(qū)與兩個Survivor區(qū)域的比值, 設(shè)置為8,則兩個Survivor區(qū)與一個Eden區(qū)的比值為2:8,一個Survivor區(qū)占整個年輕代的1/10
JVM永久代(方法區(qū))內(nèi)存參數(shù):
- -XX:PermSize 永久代的初始大小
- -XX:MaxPermSize 永久代的最大值
3.判斷對象是否有可以被回收
3.1 引用計數(shù)器算法
思路就是給對象添加一個引用計數(shù)器:當(dāng)對象被引用一次時躁愿,計數(shù)器值就增加1叛本;當(dāng)引用失效時,引用器的值就減1彤钟;任何引用計數(shù)器值為零的對象就是沒有被使用的對象来候。這種判斷對象是否被使用的方式實現(xiàn)簡單,但是存在一個問題:當(dāng)A逸雹、B兩個對象只是循環(huán)引用营搅,沒有其他任何引用云挟,這時候其實這兩個對象已經(jīng)不能被訪問到了,但是他們還是被對方所引用转质。所以Java沒有采用這種方式园欣,而是采用可達性分析算法。
3.2 可達性分析算法
這個算法以一系列的“GC Roots”對象作為起點休蟹,從這些起點開始向下搜索沸枯,搜索所走過的路徑稱為引用鏈(Reference Chain),當(dāng)一個對象從引用鏈不可達時,證明此對象是不可用的赂弓。
實際上Java1.2以后就對引用的概念進行了擴充绑榴,將引用分為強引用(Strong Reference)、軟引用(Soft Reference)盈魁、弱引用(Weak Reference)翔怎、虛引用(Phantom Reference),具體GC對幾種引用的回收機制可以參考這篇文章:JVM 引用計數(shù)、強引用备埃、弱引用姓惑、軟引用、虛引用
4.垃圾回收算法
4.1 標(biāo)記-清理算法(Mark-Sweep)
標(biāo)記清理算法分為兩步--“標(biāo)記”和“清理”:首先標(biāo)記出所有需要回收的對象按脚,在標(biāo)記完成后統(tǒng)一回收所有標(biāo)記的對象于毙。這種算法有兩個明顯的不足:一個是效率問題,標(biāo)記和清理兩個過程的效率都不高辅搬;另一個是空間問題唯沮,標(biāo)記清除后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,內(nèi)存碎片太多可能會導(dǎo)致后續(xù)分配大對象時堪遂,無法找到足夠的連續(xù)內(nèi)存介蛉,而不得提前觸發(fā)另一次垃圾回收動作。
4.2 復(fù)制算法
復(fù)制算法的思路就是溶褪,將內(nèi)存劃分為兩塊币旧,每次只是用其中的一塊。當(dāng)其中的一塊內(nèi)存用完了猿妈,就將還存活的對象復(fù)制到另一塊上面吹菱,然后再把已使用過的內(nèi)存空間一次清理掉。這樣每次清理只需要移動堆頂指針彭则,按順序分配即可鳍刷,運行效率高,且不會產(chǎn)生內(nèi)存碎片俯抖。但是他的代價是將內(nèi)存縮小為原來的一半输瓜。。。
實際上在真正垃圾回收器實現(xiàn)時尤揣,沒有必要將內(nèi)存劃分為等量的兩塊搔啊。例如,HotSpot就是將新生代劃分為Eden區(qū)和兩個Survivor區(qū)(From 和To區(qū))芹缔,默認比例為8:1:1坯癣,創(chuàng)建新對象是在Eden區(qū)域分配內(nèi)存,每次垃圾回收時最欠,將Eden區(qū)和From區(qū)中存活的對象復(fù)制到To區(qū)示罗,然后將Eden和From區(qū)清空,下一次垃圾回收時同上操作芝硬。這樣只浪費了1/10的新生代區(qū)域蚜点。
4.3 標(biāo)記-整理算法
復(fù)制算法在對象存活率高時就要進行較多的復(fù)制操作,效率就會大打折扣拌阴,對于對象存活的老年區(qū)就不太適用了绍绘,所以標(biāo)記整理算法就應(yīng)運而生了。標(biāo)記整理算法的“標(biāo)記過程”和“標(biāo)記清除”的一樣迟赃,但后續(xù)的整理階段是將存活的對象移動到一端陪拘,然后清理掉端邊界以外的內(nèi)存。
5.垃圾回收器
5.1 Serial收集器
Serial是一個單線程新生代的收集器纤壁,它在進行垃圾回收時會暫停其他所有的工作線程(Stop The World)左刽,直到它收集結(jié)束。這是一個古老的垃圾收集器酌媒,一般不會用在企業(yè)級應(yīng)用中欠痴,但對于一些內(nèi)存區(qū)域很小的桌面程序,倒是可以采用這種垃圾回收器秒咨±桑可以使用參數(shù)-XX:+UseSerialGC
使用Serial收集器。
5.2 ParNew收集器
ParNew收集器其實就是Serial的多線程版雨席,采用多線程的方式回收垃圾菩咨,同樣回收過程中,也需要暫停其他所有用戶線程陡厘〕槊祝可以使用JVM參數(shù)-XX:UsePartNewGC
指定新生代使用ParNew收集器。
5.3 Parallel Scavenge收集器
新生代垃圾回收器雏亚,采用復(fù)制算法缨硝,是一個吞吐量(運行用戶代碼的時間摩钙、(運行用戶代碼的時間+垃圾回收的時間))優(yōu)先的并行垃圾回收器罢低。他提供了控制最大垃圾收集時間的參數(shù)-XX:MaxGCOauseMillis
和直接設(shè)置吞吐量的參數(shù)-XX:GCTimeRatio
,其中-XX:MaxGCOauseMillis
是以毫秒為單位的正數(shù),-XX:GCTimeRatio
是大于0小于100的整數(shù)。
Parallel Scavenge
收集器還有一個參數(shù)-XX:+UseAdaptiveSizePolicy
,它是一個開關(guān)网持,當(dāng)它打開后就不需要手動設(shè)置新生代的大幸说骸(-Xmn)、Eden與Survicor區(qū)的比例(-XX:Survivor)功舀、晉升老年代對象年齡(-XX:PretenureSizeThreshold)等詳細參數(shù)了萍倡,虛擬機會根據(jù)當(dāng)前系統(tǒng)的運行情況、收集性能監(jiān)控信息辟汰,動態(tài)調(diào)整這些參數(shù)列敲,以提供最合適的停頓時間或者最大吞吐量,這種調(diào)節(jié)方式成為GC自適應(yīng)策略(GC Ergonomics)帖汞。
可以使用參數(shù)-XX:+UseParallelGC
指定新生代使用Parallel Scavenge收集器戴而。
5.4 Serial Old收集器
Serial Old收集器是Serial的老年代版本,通同樣使用一個線程進行垃圾回收翩蘸,采用“標(biāo)記整理”算法所意。這種垃圾收集器主要給Client模式下的虛擬機使用。但是Server模式下也有兩種使用場景:JDK1.5及以前的版本中與Parallel Scavenge收集器搭配使用催首,另一個場景是作為CMS收集器后備方案扶踊,在并發(fā)收集發(fā)生Concurrent Mode Failure時使用±扇危可以使用參數(shù)-XX:+UseSerialGC
指定老年代使用Serial old收集器秧耗。
5.5 Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年版本,使用多線程和“標(biāo)記-整理”算法涝滴。這個收集器的出現(xiàn)很大程度上解決了新生代的Parallel Scavenge收集器的尷尬地位绣版。原因是在JDK1.6以前,新生代采用Parallel Scavenge收集器歼疮,老年代的收集器只能采用Serial Old收集器杂抽,這樣的組合很大程度上還不如ParNew加CMS給力。但是1.6出現(xiàn)的Parallel Old收集器和新生代的Parallel Scavenge收集器是一個吞吐量優(yōu)先的最佳搭檔韩脏。使用參數(shù)-XX:+UseParallelGC
指定老年代使用Parallel Old收集器缩麸。
5.6 CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標(biāo)的老年代垃圾收集器。從名字(包括“Mark Sweep”)上就可以看出赡矢,CMS收集器是基于“標(biāo)記-清除”算法實現(xiàn)的杭朱,它的運作過程分為4個步驟:
- 初始標(biāo)記(CMS initial mark)
- 并發(fā)標(biāo)記(CMS concurrent mark)
- 重新標(biāo)記(CMS remark)
- 并發(fā)清除(CMS concurrent sweep)
其中,初始標(biāo)記吹散、重新標(biāo)記這兩個步驟仍需要“Stop The World”弧械,可以使用參數(shù)-XX:+UseConcMarkSweepGC
指定老年代使用CMS回收器。CMS還有一個重要的參數(shù)-XX:CMSInitiatingOccupancyFraction
,它是0-100的數(shù)空民,是觸發(fā)CMS回收老年代的閾值刃唐,即:當(dāng)老年代使用率大于改參數(shù)指定值時候羞迷,就會使用CMS回收器對老年代的垃圾進行回收。
6.JVM參數(shù)
HotSpot JVM
選項分為三類:
6.1 標(biāo)準(zhǔn)選項
這類選項的功能很穩(wěn)定画饥,在后續(xù)的版本中也不太會發(fā)生變化衔瓮。運行java -help
可以看到所有的標(biāo)準(zhǔn)選項。標(biāo)準(zhǔn)選項都是以-
開頭的抖甘,比如-version
热鞍,-server
等。
6.2 X選項
比如-Xms
衔彻。這類選項都是以-X
開頭的薇宠,也被稱為X
選項。運行java -X
命令可以查看所有的X
選項艰额。
6.3 XX選項
這類選項屬于實驗性的昼接,主要是給JVM
開發(fā)者用于開發(fā)和調(diào)試JVM
的,在后續(xù)的行為中可能會發(fā)生改變悴晰。XX選項的語法
- 如果是布爾類型的選項慢睡,它的格式為-XX:+flag或者-XX:-flag,分別表示開啟和關(guān)閉該選項铡溪。
- 針對非布爾類型選項漂辐,它的格式為-XX:flag=value。
對于HotSpot建議將最大堆和最小堆設(shè)置為相同值
- -Xms 控制Java堆的初始化值
- -Xmx 堆的最大值
- -Xmn 控制年輕代的值
- -Xss 設(shè)置java線程棧的大小
- -XX:PermSize 永久代的初始大小
- -XX:MaxPermSize 永久代的最大值
- -XX:NewRatio 年輕代與老年代的比值
- -XX:SurvivorRatio Eden區(qū)與兩個Survivor區(qū)域的比值棕硫, 設(shè)置為8,則兩個Survivor區(qū)與一個Eden區(qū)的比值為2:8,一個Survivor區(qū)占整個年輕代的1/10
- -XX:+PrintGCDetails 打印GC詳情
更多JVM參數(shù)請參閱:JVM參數(shù)設(shè)置與分析
7.參考文獻
Java中的垃圾回收機制
內(nèi)存區(qū)域 JVM運行時數(shù)據(jù)區(qū)
雜談GC
JVM系列三:JVM參數(shù)設(shè)置髓涯、分析
JVM 垃圾回收器工作原理及使用實例介紹