JAVA虛擬機之垃圾收集與內存分配策略

最近再看《深入理解JAVA虛擬機》周志明寫的第二版。現將學習筆記分享出來,方便日後複習,理解有誤的地方歡迎指正!

1、運行時數據區:

程序計數器:一塊較小的內存空間,保存當前線程所執行的字節碼的行號指示器。

java虛擬機棧:生命週期與線程相同,每個方法在執行的同事都會創建一個棧幀用於存儲局部變量、操作數棧、動態鏈接、方法出口等信息。這個區域存在2種異常情況:①線程請求的棧深度大於虛擬機允許的深度,拋出StackOverflowError.②虛擬機棧可以動態擴展,如果擴展時無法申請到足夠的內存,拋出OutOfMemoryError.

本地方法棧:虛擬機棧爲虛擬機執行Java方法服務,本地方法棧爲虛擬機使用到的Navtive方法服務。本地方法棧也會拋出StackOverflowError和OutOfMemoryError異常。

java堆:在虛擬機啓動時創建,此內存區的唯一目的就是存放對象實例,別名GC堆

方法區:用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。別名非堆

運行時常量池:是方法區的一部分,用於存放編譯期生成的各種字面量和符號引用

直接內存:並不是虛擬機運行時數據區的一部分。避免在Java堆和Native堆種來回複製數據。

2、hotspot虛擬機對象

對象的創建:虛擬機遇到new時:①在常量池中定位這個類的符號引用,並檢查這個符號代表的類是否被加載過②虛擬機爲新生對象分配內存空間③採用CAS保證內存分配的原子性,或每個線程在java堆中預先分配一小塊內存(本地線程分配緩衝)④保存類的元數據信息、對象的哈希碼、對象的GC分代年齡信息設置

對象的內存佈局:分爲3塊區域:對象頭、實例數據和對象填充

對象頭:①存儲對象自身的運行時的數據②保存類型指針(對象指向它的類的元數據的指針)

實例數據:對象真正存儲的有效信息

對象填充:不是必然存在的,沒有特別的含義,僅僅起着佔位符的作用。

對象的訪問定位:主流的訪問方式有使用句柄和直接指針兩種(詳細見書)

內存泄漏or內存溢出?

異常實踐:

堆溢出:-Xms20m -Xmx20m

棧溢出:-Xss128k

     注:-Xss設置棧內存:表示給每個線程分配得大小,值設置得越大,多線程時oom的可能性越高。

方法區和運行時常量池溢出:在JDK1.6版本,常量池被分配在永久帶。1.7常量池屬於方法區的一部分

3、垃圾收集器與內存分配策略

①判斷對象是否還存活(不再被任何途徑使用的對象)

引用計數算法:給對象添加一個引用計數器,每次被引用+1,引用失效-1,計數器爲0時就是不再被使用的,進行gc。優點:效率高,但是主流的虛擬機沒有選用引用計數法來管理內存,因爲很難解決對象和之間互相引用的問題。

可達性分析算法:通過GC Roots對象作爲起始節點,向下搜索(搜索走過的路徑稱爲引用鏈)對象,從GC Roots到這個對象不可達時,證明這個對象是不可用的。

注:在java中,可作爲GC Roots的對象包括:

虛擬機棧(棧幀中的本地變量表)中引用的對象

方法區中類靜態屬性引用的對象

方法區中常量引用的對象

本地方法棧中JNI(即一般說的Native方法)引用的對象

缺點:可達性算法必須保證在一致性的快照中進行,避免分析過程中對象的引用關係還在不斷髮生着變化。這點導致GC進行時必須停頓所有java線程(stop the world)

生存還是死亡(是否要在本次被回收):

不可達的對象會經歷2次標記的過程:

<1>第一次標記且篩選(篩選條件:是否有必要執行finalize()方法)

<2>如果有必要執行,將對象放到F-Queue隊列之中,稍後GC會對F-Queue隊列中的對象進行第二次標記。如果次是對象與引用鏈上的對象關聯,這個對象將會被移出“即將回收的集合”,否則就會被執行GC

②垃圾收集算法

標記-清除算法:先標記需要回收的對象,然後統一清除

缺點:效率不高(2個過程的效率都不高)、空間問題(清除之後會產生大量碎片)

複製算法:將可用內存按容量大小分爲相等的2塊,每次只使用其中的一塊,當這一塊的內存用完了,就將還存活的對象複製到另一塊上,然後把已用的內存空間一次性清理掉。(現在商業虛擬機都採用這種方式來回收新生代

通常將內存分爲一塊較大的Eden空間和2塊較小的survivor空間,每次使用Enden和其中一塊survivor。

E+S1->S2  E+S2->S1   HotSpot默認E和S的大小比例是8:1

如果另一塊Survivor空間沒有足夠空間存放上一次新生代手機下來的存活對象,就通過分配擔保機制進入老年代。

標記-整理算法:先標記需要回收的對象,讓所有存活的對象都向一端移動,然後直接清理掉邊界以外的內存。

分代收集算法:新生代只有少量存活,選用複製算法。老年代存活率高,沒有額外空間分配擔保,選用標記整理算法。

③HotSpot算法實現

枚舉根節點:HotSpot引用一組OopMap的數據結構,在類加載完成的時候,就把對象內什麼偏移量上是什麼類型的數據計算出來。在JIT編譯過程中,也會在特定的位置記錄下棧和寄存器中哪些位置是引用。

注:由於可達性算法對時間的敏感性,即使號稱(幾乎)不會發生停頓的CMS收集器中,枚舉根節點也是要必須停頓的。

安全點:HotSpot並沒有爲每一條指令都生成OopMap,只是在安全點才記錄這些信息,也就是說程序只有到達安全點時才能停頓下來開始GC.安全點的選定標準“是否具有讓程序長時間執行的特徵”。例如方法調用、循環跳轉、異常跳轉等具有長時間執行的功能的指令纔會產生安全點。

如何讓GC發生時,所有線程都跑到最近的安全點上停下來?

搶先式中斷:把所有線程都中斷,如果又不在安全點上的就恢復線程,讓它跑到安全點上。(幾乎沒有虛擬機使用這種方式)

主動式中斷:不直接對線程操作,在安全點和創建對象需要分配內存的地方設一個標誌,各線程主動輪詢這個標誌,發現中斷標識時自己中斷掛起。

如果程序沒有分配CPU時間(sleep或blocked),就無法響應JVM的中斷請求。此時需要安全區域來解決。

安全區域:是指在一段代碼片段中,引用關係不會發生變化,在這個區域中的任意地方開始GC都是安全的。在線程執行到安全區域時,先給自己打個標識,如果這段時間JVM要發起GC,就不需要管已經打標識的線程。在線程要離開安全區域時,要先檢查

系統是否已經完成了根節點枚舉(或者整個GC過程),如果沒完成就需要等待接受可以離開安全區域的信號。

④垃圾收集器

serial、parnew、parallel scavenge、serial old、parallel old、cms收集器

並行:指多條垃圾收集線程並行工作,但此時用戶線程仍處於等待狀態

併發:指用戶線程與垃圾收集線程同時執行(但不一定是並行的,可能會交替執行),用戶程序在繼續運行,而垃圾收集程序運行在另一個CPU上。

CMS收集器:基於“標記-清除”算法實現的。

<1>初始標記:stop the world,標記GC Root能直接關聯到的對象,速度快

<2>併發標記:進行gc roots tracing

<3>重新標記:stop the world,標記併發標記期間程序運行導致標記產生變動的那一部分對象的標記記錄。這個階段的停頓時間一般比初始標記階段稍長,但是遠比並發標記時間短。

<4>併發清除:

參數-XX:CMSInitiatingOccupancyFraction老年代內存空間百分比閾值觸發CMS收集器進行GC

參數-XX:UseCMSCompactAtFullCollection開關(默認開始)在CMS要開始Full GC之前開啓內存碎片的合併整理

參數-XX:CMSFullGCsBeforeCompaction用於設置執行多少次不壓縮的Full GC後執行一次壓縮(默認0,表示每次進入Full GC都進行碎片整理)

G1收集器特點:

並行與併發:利用多cpu多核的硬件優勢

分代收集:保留分代收集的概念

空間整合:G1運作期間不會產生內存空間碎片

可預測的停頓:G1在後臺維護一個優先級列表,優先回收價值最大的Region(garage-first)提高回收效率,可以建立可預測的停頓時間模型

G1收集器運作步驟:初始標記、併發標記、最終標記、篩選回收

理解GC日誌:

①前面的數字:代表了GC發生的時間,從java虛擬機啓動以來經過的秒數

②GC日誌開頭的 [GC 和 [Full GC 代表停頓類型,如果有Full 代表發生了stop the world

③[DefNew:不同收集器定義的新生代、老年代、永久代名字,表示GC發生的區域

④方括號內部:3324K -> 152K(3712K)  GC前該內存區域已使用容量->GC後改內存區域已使用容量(該內存區域總容量)

方括號之外:GC前Java堆已使用容量->GC後Java堆已使用容量(Java堆總容量)

⑤0.0025825 secs 表示該內存區域GC所佔用的時間,單位是秒

垃圾收集器參數總結:

 

內存分配與回收策略:

①對象優先在Eden分配

②大對象直接進入老年代:-XX:PretenureSizeThreshold參數可以設置令大於這個參數值的對象直接在老年代分配。

③長期存活的對象將進入老年代:-XX:MaxTenuringThreshold參數可以設置對象在新生代存活超過多少次進入老年代。

④動態對象年齡判定:如果survivor空間中相同年齡所有對象大小的綜合大於survivor空間的一半,年齡大於等於該年齡的對象就可以直接進入老年代。

⑤空間分配擔保:發生Minor GC之前會檢查老年代的空間是否大於新生代,如果比新生代大就進行Minor GC,如果比新生代小就看HandlePromotionFailure設置是否允許冒險,如果允許冒險就檢查老年代剩餘的連續內存大小是否比歷代進入老年代的內存大,符合就Minor GC,否則就Full GC。(見下面流程圖)

注:

新生代GC(Minor GC):指發生在新生代的GC。執行頻繁,回收速度較快。

老年代GC(Major GC/Full GC):指發生在老年代的GC,老年代GC一般比新生代GC慢10倍

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章