jvm gc垃圾回收机制和参数说明amp;amp;Java JVM 垃圾回收(GC 在什么时候,对什么东西,做了什么事情)
jvm?gc(垃圾回收機(jī)制)
Java JVM? 垃圾回收(GC 在什么時(shí)候,對什么東西,做了什么事情)
- 前言:(先大概了解一下整個(gè)過程)
- 作者:知乎用戶
鏈接:https://www.zhihu.com/question/27339390/answer/36511809
來源:知乎
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
?java堆(JavaHeap)
<img src="https://pic1.zhimg.com/50/f7541e5d33d1d8b412dd0556c7e4b10d_hd.jpg" data-rawwidth="481" data-rawheight="713" class="origin_image zh-lightbox-thumb" width="481" data-original="https://pic1.zhimg.com/f7541e5d33d1d8b412dd0556c7e4b10d_r.jpg">圖來了我就不用多說了。每個(gè)棧幀其實(shí)可以理解為一個(gè)方法,我是這么理解的,之間的關(guān)系就是調(diào)用。
1.用來存放對象的,幾乎所有對象都放在這里,被線程共享的,或者說是被棧共享的
2.堆又可以分為新生代和老年代,實(shí)際還有一個(gè)區(qū)域叫永久代,但是jdk1.7已經(jīng)去永久代了,所以可以當(dāng)作沒有,永久代是當(dāng)jvm啟動時(shí)就存放的JDK自身的類和接口數(shù)據(jù),關(guān)閉則釋放。
新生代可以分為Eden區(qū)和兩個(gè)幸存區(qū),這么設(shè)計(jì)是為了更好地利用內(nèi)存 之前的設(shè)計(jì)是只分為兩部分一樣一半 后來發(fā)現(xiàn)這樣只利用到了一半的內(nèi)存 才改為按比例分成三個(gè)區(qū)的,使用的是復(fù)制回收算法,兩個(gè)幸存區(qū)是較小的區(qū)域。邏輯是每次使用Eden區(qū)和其中一個(gè)幸存區(qū),回收時(shí)將其還存活著的對象一次性的復(fù)制到另一個(gè)幸存區(qū)中,最后清理到剛才使用的Eden和其中一個(gè)幸存區(qū)。
美團(tuán)的面試官也問了這個(gè)問題,他也說了他的理解,我感覺可能是不準(zhǔn)確的。
新建對象就在Eden區(qū),Eden就是伊甸,顧名思義。但是并不是對象最活躍的區(qū)域,對象最活躍的區(qū)域是老年代,因?yàn)榻?jīng)過各種垃圾回收之后對象都跑到這里來了。
3.內(nèi)存溢出
內(nèi)存溢出其實(shí)沒什么好講的,滿了就會溢出。怎么才能滿呢,不斷創(chuàng)建對象,那問題又來了,創(chuàng)建多了被回收怎么辦,好辦,將新建的對象存到list里去,就不會回收了,為什么呢,因?yàn)閖vm判定一個(gè)對象的死活就是根據(jù)對象是不是被引用。
此外堆跟隨jvm的,有jvm就有堆。堆也是垃圾回收的主要區(qū)域,又叫GC堆,垃圾堆,玩笑。
jvm棧
1.要說棧是用來存什么的,其實(shí)我感覺不嚴(yán)謹(jǐn),棧是運(yùn)行時(shí)創(chuàng)建的,是跟隨線程的,它不是用來存什么的,那它用來干什么的,它是用來存棧幀的,沒有圖不太好說呢,等下我去截個(gè)圖。
2.棧的好處就是不需要垃圾回收,隨著線程結(jié)束內(nèi)存就釋放。
3.但是并不是說就不會內(nèi)存溢出,那么棧的內(nèi)存溢出是怎么產(chǎn)生的呢,肯定也是滿了,這個(gè)滿了怎么理解呢,一是要申請的不夠了,二是jvm內(nèi)存太小,這是個(gè)有趣的問題。但是產(chǎn)生的錯(cuò)誤卻是不一樣的,如果創(chuàng)建一個(gè)void方法調(diào)用自身,錯(cuò)誤是stackoverflowError,如果不斷創(chuàng)建線程則會outOfMemoryError。這里就有一個(gè)比較高級的問題了,對于第二種多線程內(nèi)存溢出該怎么解決呢,深入理解jvm一書中給出的解決方案是這樣的,通過減小最大堆和棧容量來換取更多的線程。
方法區(qū)和運(yùn)行時(shí)常量池
1.方法區(qū)是堆的一個(gè)邏輯區(qū)域,但是又叫非堆。運(yùn)行時(shí)常量池又是方法區(qū)的一部分,真正的一部分。方法區(qū)并不是存方法的,存方法的應(yīng)該是棧或者棧幀。方法區(qū)存的是類信息、常量、靜態(tài)變量等,也是被線程共享的區(qū)域。運(yùn)行時(shí)常量池存放的是編譯期生產(chǎn)的各種字面量和符號引用。
2.這塊內(nèi)存區(qū)域的回收沒啥好說的,因?yàn)槲乙膊惶宄?#xff0c;我只知道HotSpot的設(shè)計(jì)團(tuán)隊(duì)選擇把GC分代擴(kuò)展至方法區(qū)了,或者是使用永久代實(shí)現(xiàn)方法區(qū)。
3.內(nèi)存是肯定會溢出的,不斷創(chuàng)建類會導(dǎo)致方法區(qū)內(nèi)存溢出,而不斷將常量放入常量池(String.intern()),常量池也會內(nèi)存溢出。 -
?
- 這里主要講分代回收機(jī)制
- 年輕代:一個(gè) Eden 區(qū)和兩個(gè) Survivor 區(qū)
- 年老代:一個(gè)?old 區(qū)
- 持久代:一個(gè) Permanent 區(qū)
新建立的對象先放到 Eden 區(qū)中,如果 Eden 區(qū)滿了之后,就會執(zhí)行標(biāo)記-清除算法回收 Eden 區(qū)垃圾,并把生存的對象放到 Survivor 的其中一個(gè)區(qū)中,兩個(gè) Survivor 區(qū)有一個(gè)必須是空的,當(dāng)其中一個(gè) Survivor 滿了之后,采用標(biāo)記-復(fù)制方法,把生存的對象放到另外一個(gè) Survivor 區(qū)。
在年輕代中經(jīng)歷了N次垃圾回收后仍然存活的對象,就會被放到年老代中。
持久代用于存放靜態(tài)文件、Java類、方法、靜態(tài)對象等。
- 觸發(fā) gc 的條件
minor GC: 當(dāng)新對象生成,并且在Eden申請空間失敗時(shí),就會觸發(fā)minor GC,對Eden區(qū)域進(jìn)行GC,清除非存活對象,并且把尚且存活的對象移動到Survivor區(qū)。然后整理Survivor的兩個(gè)區(qū)
Full GC: 對整個(gè)堆進(jìn)行整理,包括Young、old 和Perm。Full GC因?yàn)樾枰獙φ麄€(gè)對進(jìn)行回收,以下原因可能導(dǎo)致 Full GC
- 年老代(old )被寫滿
- 持久代(Perm)被寫滿
- System.gc()被顯示調(diào)用
- 上一次GC之后Heap的各域分配策略動態(tài)變化
?
jvm參數(shù)說明~~
?
?
1.jvm的結(jié)構(gòu):
?
-
方法區(qū): 也是 jvm gc 中的持久代,它用于存儲虛擬機(jī)加載的類信息、常量、靜態(tài)變量、是各個(gè)線程共享的內(nèi)存區(qū)域。
-
堆: 也是被各個(gè)線程共享的內(nèi)存區(qū)域,在JVM啟動時(shí)創(chuàng)建。該內(nèi)存區(qū)域存放了對象實(shí)例及數(shù)組,包括 jvm gc 中的年輕代和年老代。
-
虛擬機(jī)棧: 每個(gè)方法被執(zhí)行的時(shí)候 都會創(chuàng)建一個(gè)“棧幀”用于存儲局部變量表(包括參數(shù))、操作棧、方法出口等信息。每個(gè)方法被調(diào)用到執(zhí)行完的過程,就對應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過程。聲明周期與線程相同,是線程私有的。而局部變量表中繼承 Object 的對象都是引用堆和方法區(qū)的內(nèi)存,而基本數(shù)據(jù)類型的對象(boolean、byte、char、short、int、float、long、double)則是直接保存存在棧中。
-
本地方法棧: 與虛擬機(jī)棧基本類似,區(qū)別在于虛擬機(jī)棧為虛擬機(jī)執(zhí)行的java方法服務(wù),而本地方法棧則是為Native方法服務(wù)。
-
程序計(jì)數(shù)器: 類似于PC寄存器,是一塊較小的內(nèi)存區(qū)域,通過程序計(jì)數(shù)器中的值尋找要執(zhí)行的指令的字節(jié)碼,由于多線程間切換時(shí)要恢復(fù)每一個(gè)線程的當(dāng)前執(zhí)行位置,所以每個(gè)線程都有自己的程序計(jì)算器。
?
-
-Xmx: 堆內(nèi)存大小的上限
-
-Xms: 堆內(nèi)存大小的初始值
-
-Xmn: 年輕代內(nèi)存大小,年輕代包括兩個(gè)區(qū),Eden 和 Survivor 區(qū),Suvrvior 區(qū)還被平均分成了兩塊 from space 和 to space
-
-Xss: 每條線程內(nèi)存大小
-
-XX:PermSize(java8 之后變成了 -XX:MetaspaceSize): 持久代初始內(nèi)存大小
-
-XX:MaxPermSize(java8 之后變成了 -XX:MaxMetaspaceSize): 最大持久代內(nèi)存大小
-
-XX:NewSize: 新生代初始堆內(nèi)存占用的默認(rèn)值
-
-XX:MaxNewSize: 新生代占整個(gè)堆內(nèi)存的最大值
-
-XX:NewRatio: 老年代對比新生代的空間大小, 比如2代表老年代空間是新生代的兩倍大小
-
-XX:SurvivorRatio: Eden/Survivor的值,比如8表示Survivor:Eden=1:8, 因?yàn)閟urvivor區(qū)有2個(gè), 所以Eden的占比為8/10
-
-XX:CompressedClassSpaceSize: 類指針壓縮空間大小
- 64位平臺上默認(rèn)打開
- 使用-XX:+UseCompressedOops壓縮對象指針?
"oops"指的是普通對象指針("ordinary" object pointers)?
Java堆中對象指針會被壓縮成32位?
使用堆基地址(如果堆在低26G內(nèi)存中的話,基地址為0) - 使用-XX:+UseCompressedClassPointers選項(xiàng)來壓縮類指針?
對象中指向類元數(shù)據(jù)的指針會被壓縮成32位?
類指針壓縮空間會有一個(gè)基地址
?
?
-
類指針壓縮空間只包含類的元數(shù)據(jù),比如InstanceKlass, ArrayKlass?
僅當(dāng)打開了UseCompressedClassPointers選項(xiàng)才生效?
為了提高性能,Java中的虛方法表也存放到這里 -
元空間包含類的其它比較大的元數(shù)據(jù),比如方法,字節(jié)碼,常量池等
?
?
?
Java JVM? 垃圾回收(GC 在什么時(shí)候,對什么東西,做了什么事情)
?
?
在什么時(shí)候
首先需要知道,GC又分為minor GC 和 Full Gc(也稱為Major GC)。Java 堆內(nèi)存分為新生代和老年代(持久代在方法區(qū)上),新生代中又分為1個(gè)Eden區(qū)域 和兩個(gè) Survivor區(qū)域。
那么對于 Minor GC 的觸發(fā)條件:大多數(shù)情況下,直接在 Eden 區(qū)中進(jìn)行分配。如果 Eden區(qū)域沒有足夠的空間,那么就會發(fā)起一次 Minor GC;
?
對于 Full GC(Major GC)的觸發(fā)條件:也是如果老年代沒有足夠空間的話,那么就會進(jìn)行一次 Full GC。
Ps:上面所說的只是一般情況下,實(shí)際上,需要考慮一個(gè)空間分配擔(dān)保的問題:
在發(fā)生Minor GC之前,虛擬機(jī)會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象的總空間。如果大于則進(jìn)行Minor? GC,如果小于則看HandlePromotionFailure設(shè)置是否允許擔(dān)保失敗(不允許則直接Full GC)。如果允許,那么會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小,如果大于則嘗試Minor GC(如果嘗試失敗也會觸發(fā)Full GC),如果小于則進(jìn)行Full GC。
(空間分配擔(dān)保總結(jié):
minor?GC :1.?老年代最大連續(xù)可用的空間大于新生代所有對象的總空間; 2.?老年代最大連續(xù)可用空間小于新生代所有對象的總空間,并且HandlePromotionFailure設(shè)置允許擔(dān)保失敗,且老年代最大可用連續(xù)空間大于歷次晉升老年代對象的平均大小。
full?GC :1.?老年代最大連續(xù)可用的空間小于新生代所有對象的總空間,并且HandlePromotionFailure設(shè)置不允許擔(dān)保失敗;
2。?如果上面的2嘗試minor?GC失敗,則出發(fā)full?GC。
?
?
但是,具體到什么時(shí)刻執(zhí)行,這個(gè)是由系統(tǒng)來進(jìn)行決定,是無法預(yù)測的。
對什么東西(總結(jié):從GC?root開始搜索,搜索不到的對象,并且經(jīng)過第一個(gè)標(biāo)記,清理之后,仍然沒有復(fù)活的對象)
?
主要根據(jù)可達(dá)性分析算法,如果一個(gè)對象不可達(dá),那么就是可以回收的;如果一個(gè)對象可達(dá),那么這個(gè)對象就不可以回收。對于可達(dá)性分析算法,它是通過一系列稱為“GC Roots” 的對象作為起始點(diǎn),當(dāng)一個(gè)對象到 GC Roots 沒有任何引用鏈相接的時(shí)候,那么這個(gè)對象就是不可達(dá),就可以被回收。如下圖:
?
這個(gè)GC Root 對象可以是一些靜態(tài)的對象,Java方法的local變量或參數(shù), native 方法引用的對象,活著的線程。
做了什么事情
主要做了清理對象,整理內(nèi)存的工作。Java堆分為新生代和老年代,采用了不同的回收方式。例如新生代采用了標(biāo)記復(fù)制算法,老年代采用了標(biāo)記整理法。在新生代中,分為一個(gè)Eden 區(qū)域和兩個(gè)Survivor區(qū)域,真正使用的是一個(gè)Eden區(qū)域和一個(gè)Survivor區(qū)域,GC的時(shí)候,會把存活的對象放入到另外一個(gè)Survivor區(qū)域中,然后再把這個(gè)Eden區(qū)域和Survivor區(qū)域清除。那么對于老年代,采用的是標(biāo)記整理法,首先標(biāo)記出存活的對象,然后再移動到一端。這樣也有利于減少內(nèi)存碎片。
?
?
?
?
額外補(bǔ)充:
?
?
比如GC的時(shí)候必須要等到Java線程都進(jìn)入到safepoint的時(shí)候VMThread才能開始執(zhí)行GC,
串行收集器:串行收集器使用一個(gè)單獨(dú)的線程進(jìn)行收集,GC時(shí)服務(wù)有停頓時(shí)間
并行收集器:次要回收中使用多線程來執(zhí)行
CMS收集器是基于“標(biāo)記—清除”算法實(shí)現(xiàn)的,經(jīng)過多次標(biāo)記才會被清除
G1從整體來看是基于“標(biāo)記—整理”算法實(shí)現(xiàn)的收集器,從局部(兩個(gè)Region之間)上來看是基于“復(fù)制”算法實(shí)現(xiàn)的
[GC收集器]: http://www.jianshu.com/p/50d5c88b272d
加載、驗(yàn)證、準(zhǔn)備、解析、初始化。然后是使用和卸載了
通過全限定名來加載生成class對象到內(nèi)存中,然后進(jìn)行驗(yàn)證這個(gè)class文件,包括文件格式校驗(yàn)、元數(shù)據(jù)驗(yàn)證,字節(jié)碼校驗(yàn)等。準(zhǔn)備是對這個(gè)對象分配內(nèi)存。解析是將符號引用轉(zhuǎn)化為直接引用(指針引用),初始化就是開始執(zhí)行構(gòu)造器的代碼
Bootstrap ClassLoader:啟動類加載器,負(fù)責(zé)將$ Java_Home/lib下面的類庫加載到內(nèi)存中(比如rt.jar
Extension ClassLoader:標(biāo)準(zhǔn)擴(kuò)展(Extension)類加載器,它負(fù)責(zé)將$Java_Home /lib/ext或者由系統(tǒng)變量 java.ext.dir指定位置中的類庫加載到內(nèi)存中。
ApplicationClassLoader:它負(fù)責(zé)將系統(tǒng)類路徑(CLASSPATH)中指定的類庫加載到內(nèi)存中。開發(fā)者可以直接使用系統(tǒng)類加載器
雙親委派模型是某個(gè)特定的類加載器在接到加載類的請求時(shí),首先將加載任務(wù)委托給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務(wù),就成功返回;只有父類加載器無法完成此加載任務(wù)時(shí),才自己去加載。-----例如類java.lang.Object,它存在在rt.jar中,無論哪一個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的Bootstrap ClassLoader進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。相反,如果沒有雙親委派模型而是由各個(gè)類加載器自行加載的話,如果用戶編寫了一個(gè)java.lang.Object的同名類并放在ClassPath中,那系統(tǒng)中將會出現(xiàn)多個(gè)不同的Object類,程序?qū)⒒靵y
靜態(tài)分派(重載)與動態(tài)分派(重寫)。
設(shè)定堆最小內(nèi)存大小-Xms
新生代不宜太小,否則會有大量對象涌入老年代
-XX:NewSize:新生代大小
-XX:NewRatio 新生代和老生代占比
-XX:SurvivorRatio:伊甸園空間和幸存者空間的占比
? 年輕代用 -XX:+UseParNewGC (串行) 年老代用-XX:+UseConcMarkSweepGC (CMS)
多線程下關(guān)閉偏向鎖,比較浪費(fèi)資源
1CMS是一種以最短停頓時(shí)間為目標(biāo)的收集器
響應(yīng)優(yōu)先選擇CMS,吞吐量高選擇G1
用jmap看內(nèi)存情況,然后用 jstack主要用來查看某個(gè)Java進(jìn)程內(nèi)的線程堆棧信息
?
參考:?http://icyfenix.iteye.com/blog/715301
?
https://github.com/konginyan/Learning-Notes/blob/master/java/jvm%20%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E.md
?
總結(jié)
以上是生活随笔為你收集整理的jvm gc垃圾回收机制和参数说明amp;amp;Java JVM 垃圾回收(GC 在什么时候,对什么东西,做了什么事情)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot————Sprin
- 下一篇: MySql主从同步最小配置