一、Java內存模型(JMM)
????????JMM(Java Memory Model,Java 內存模型)是 Java 虛擬機規范中定義的一種抽象概念,用于規范 Java 程序中多線程對共享內存的訪問規則,解決可見性、原子性和有序性問題,確保 Java 程序在不同硬件和操作系統上都能獲得一致的并發行為。
運行時數據區劃分
? ? ? ? 線程私有:程序計數器、本地方法棧、虛擬機棧
????????線程公有:堆、元空間
程序計數器
? ? ? ? 字節碼解釋器在解釋執行字節碼文件工作時,當需要需要執行一條字節碼指令時,可以通過改變程序計數器的值來完成。
虛擬機棧
? ? ? ? 虛擬機棧是由一個個棧幀組成,而每個棧幀中都有:局部變量、操作數棧、動態鏈接、方法出口信息。每個方法調用時都會入棧,每個方法被調用結束后則會出棧,這樣可以清楚的看出方法之間的調用關系。
本地方法棧
? ? ? ? native關鍵字修飾的本地方法被執行時候,在本地方法棧中創建一個棧幀,用于存放發native本地方法的局部變量、操作數棧、動態鏈接、方法出口信息。方法執行完畢后,棧幀會釋放空間。
堆(Heap)
? ? ? ? 用于存放對象實例和數組的內存區域。
1、新生代、老年代
? ? ? ? 由于垃圾回收的需要,避免頻繁GC所以將堆分為新生代和老年代,其中新生代占1/3,老年代占2/3。而新生代又被分為Eden和Survivor區(from、to),占比為8:1:1。
2、對象創建時的內存分配
? ? ? ? 當創建一個新對象時會先看Eden區有沒有空間進行存儲,若沒有則執行YGC,執行后若還不能放下則看老年區是否可以放下,如果老年區不能放下,則執行老年區的FGC,執行后如果還不能不能放下,則會拋出OOM異常。
二、GC垃圾回收器
判讀對象是否存活
1、引用計數算法
? ? ? ? 在對象中添加一個引用計數器,如果對象被引用則+1,如果失去引用則-1,當我們需要判斷對象是否存活時,只需看該計算器的值是否為0。該方法不用于java中。
? ? ? ? 缺點:可能會出現循環引用的情況,從而出現死鎖。也就是a引用了b,b也引用了a。
2、可達性分析算法
? ? ? ? 通過定義一系列的" GC Roots "的根結點來作為開始結點集,當需要判斷對象是否存活時,則從根結點向下遍歷,沒有被遍歷到的則是可以被回收的垃圾對象。該方法用于java中。
3、java中四種引用類型
- 強引用
????????最常見的引用類型,直接通過?new
?關鍵字創建的對象引用。
// 強引用
Object object = new Object();
? ? ? ? 強引用它永遠不會被GC回收,當我們需要他被回收時,我們可以對其進行弱化。
// 弱化
object = null;
- 軟引用
? ? ? ? 該引用類型可以根據內存的大小來判斷是否進行GC回收,當內存充足時不進行回收,當內存不足時進行回收。
// 軟引用
SoftReference<Object> softRef = new SoftReference<>(new Object());
Object obj = softRef.get(); // 獲取軟引用指向的對象
- 弱引用
???????? 該引用類型無論是否內存充足只要發生GC就會被回收。例如:ThreadLocal中應用了弱引用
如果不使用對象后沒有及時進行銷毀,當發生GC時就會出現大量的null-value的鍵值對,從而導致發生OOM。
// 弱引用
WeakReference<Object> weakRef = new WeakReference<>(new Object());
Object obj = weakRef.get(); // 獲取弱引用指向的對象
- 虛引用
? ? ? ? 虛引用主要用來跟蹤對象被垃圾回收的活動,可以在垃圾收集時收到一個系統通知。
// 虛引用
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);
垃圾回收算法
復制算法
????????將內存空間劃分為?兩個相等的區域(如 From 空間和 To 空間),每次只使用其中一個區域:
- 當該區域內存用完時,將存活對象復制到另一個區域。
- 清空原區域的所有內存,完成垃圾回收。
缺點:
- 內存利用率低:需要預留一半內存空間,實際可用內存僅為總空間的 50%。
- 當存活對象較多時,復制成本高。
標記---清除算法
主要分兩個階段進行:
- 標記階段:從根對象(如棧變量、靜態變量)出發,遍歷所有可達對象,并標記為 “存活”。
- 清除階段:掃描整個堆內存,將未被標記的對象(即垃圾)回收,并釋放其占用的內存空間。
缺點:
- 內存碎片化:回收后會產生大量不連續的內存碎片,可能導致后續無法分配大對象。
- 效率較低:需要遍歷兩次內存(標記和清除)。
標記---整理算法
主要分兩個階段進行:
- 標記階段:同標記 - 清除算法,標記所有存活對象。
- 整理階段:將所有存活對象向內存一端移動,然后直接清理邊界外的所有內存(即垃圾對象占用的空間)。
缺點:
- 性能開銷大:需要移動對象,涉及內存復制,成本較高。
總結:由于不同算法的邏輯有所不同,所以他們的用處也有所不同。由于在新生代中的對象總是朝生夕滅,所以通常我們會使用復制算法進行垃圾回收;而老年代中的對象可以長期存活,所以我們通常適用標記---整理和標記清除算法。
?
垃圾收集器
Serial收集器(新生代)
? ? ? ?該收集器主要對新生代的垃圾進行收集清理,采用" 復制?"算法進行實現。
Serial Old收集器(老年代)
????????該收集器主要對老年代的垃圾進行收集清理,采用" 標記---整理?"算法進行實現。
? ? ? ? 他們兩個是串行收集器,也可以理解為單線程收集器,但在進行GC時會發生STW所以現在一般不使用。
Parallel Scavenge收集器(新生代)
? ? ? ? 該收集器主要對新生代的垃圾進行收集清理,采用" 復制?"算法進行實現。
Parallel Old收集器(老年代)
????????該收集器主要對老年代的垃圾進行收集清理,采用" 標記---整理?"算法進行實現。
????????他們兩個是多線程收集器,但在進行GC時會發生STW,但相對于前面性能會好很多。但是該收集器主要強調系統的吞吐量,所以會很容易出現系統上線后,內存占用過高的情況。
CMS收集器(老年代)
? ? ? ? 該收集器是一個基于" 標記---清除 "算法實現,是一種獲取最短回收停頓(STW)為目標的收集器,它可以實現讓收集線程和用戶線程并發。
????????CMS 的執行過程分為四個主要階段:
初始標記(Initial Mark, STW):通過 GC Roots 標記直接關聯到根對象的存活對象。
并發標記(Concurrent Mark):與應用線程并發執行,遍歷所有可達對象并標記存活對象。
重新標記(Remark, STW):修正并發標記期間因應用線程修改引用導致的標記錯誤。
并發清除(Concurrent Sweep)特點:無需暫停,但可能產生 “浮動垃圾”(即在清除階段新產生的垃圾,需等到下一次 GC 處理)。
? ? ? ? 由于該?標記---清除 算法所以會產生大量的內存空間碎片導致,空間的浪費;而且在并發清除的時候可能會產生浮動垃圾。
G1收集器(老年代)
? ? ? ? 該搜集器主要針對大內存的機器,它采用局部性的收集思想將一塊大內存劃分為若干大小相同的獨立區域(Region),再講這些獨立的區域收集成一個回收集合,再進行處理。
G1 的垃圾回收周期主要分為以下階段:
初始標記(Initial Mark, STW):通過 GC Roots 標記直接關聯到根對象的存活對象。
并發標記(Concurrent Marking):與應用線程并發執行,遍歷所有可達對象并標記存活對象。
最終標記(Final Mark, STW):處理并發標記期間的引用變化(即未被標記的新對象),完成標記過程。
篩選回收(Cleanup and Evacuation, STW):根據 Region 的回收價值(垃圾占比)排序,選擇部分 Region 進行回收。
?