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