是先有垃圾回收器再有JVM呢,還是先有JVM再有垃圾回收器呢?或者是先有垃圾回收再有JVM呢?歷史上還真是垃圾回收更早面世,垃圾回收最早起源于1960年誕生的LISP語言,Java只是支持垃圾回收的其中一種。下面我們就來刨析刨析JVM的垃圾回收~
文章目錄
- 1. 判斷可回收對象
- 1.1 引用計數法
- 1.2 可達性分析算法
- 2. 垃圾回收器
- 2.1 垃圾回收區域
- 2.2 回收永久代
- 2.3 垃圾回收器
- 2.4 CMS原理
- 2.5 CMS的缺點
- 2.6 G1垃圾回收器
- 3. 垃圾回收算法
- 3.1 優化復制算法
- 未完待續。。。
1. 判斷可回收對象
1.1 引用計數法
面試官:JVM為什么不采用引用計數法?
每個Java對象在引用計數法里都有一個引用計數器,引用失效則計數器 - 1,有新的引用則計數器 + 1,通過計數器的數值來判斷該對象是否是可回收對象。
大家看下這個例子,如果對象A和對象B沒有被任何對象引用,也沒有被任何線程訪問,這兩個對象按理應該被回收。但如果對象A的成員變量引用了對象B,對象B的成員變量引用了對象A,它們的引用計數器數值都不為0,通過引用計數法并不能將其視為垃圾對象。
class A {B b = new B();}class B {A a = new A();}
就因為引用計數法很難解決對象之間相互循環引用的問題,所以目前JVM采用可達性分析算法來判斷Java對象是否是可回收對象。
1.2 可達性分析算法
面試官:那你講講可達性分析算法?
可達性分析顧名思義就是以某個起始點來判斷它是否可達,這個起始點稱為GC Roots。如果Java對象不能從GC Roots作為起始點往下搜索到,那該對象就被視為垃圾對象,即可回收對象。
可以作為GC Roots對象一共包括以下四種,這點也是面試官常問的:
- 虛擬機棧中引用的對象。
- 本地方法棧中引用的對象。
- 方法區中類靜態屬性引用的對象。
- 方法區中常量引用的對象。
2. 垃圾回收器
2.1 垃圾回收區域
面試官:垃圾回收器回收的是哪個區域?
JVM由五大區域組成:堆內存、方法區、程序計數器、虛擬機棧、本地方法棧。先說結論,垃圾回收器回收的是堆內存和方法區兩大區域。
程序計數器、虛擬機棧、本地方法棧的內存分配和回收都具備確定性,都是隨著線程銷毀而銷毀,因此不需要進行回收。
但在堆內存、方法區中,內存分配和回收都是動態的,我們只有在運行期間才能知道會創建哪些對象;另外這些垃圾對象不會自動銷毀,如果任由這兩部分區域的垃圾對象不管,勢必造成內存的浪費甚至有內存泄漏的可能。
垃圾回收器存在的意義就是通過自動檢測和回收這些垃圾對象,來減少內存泄漏的風險。
2.2 回收永久代
面試官:那永久代不會進行垃圾回收對吧?
雖然永久代的垃圾回收效率是比較低的,但永久代里的廢棄常量和無用的類仍然會被回收。
例如創建一個字符串常量name,該字符串會存在于常量池中。如果該字符串沒有任何String對象去引用它,當發生內存回收時有必要會清除該廢棄常量。
private static final String name = "JavaGetOffer";
2.3 垃圾回收器
面試官:你說說都有哪些垃圾回收器?
目前市面上共有七種垃圾回收器。
-
Serial是一個作用在新生代的單線程垃圾回收器。在垃圾回收期間系統的所有線程都會阻塞,因此垃圾回收效率也相對較高。
-
ParNew則是Serial的多線程版本。這也是第一款并發的垃圾回收器,相比Serial來說垃圾回收不需要阻塞所有線程,第一次實現了讓垃圾回收線程和用戶線程同時工作。
-
Serial Old是Serial的老年代版本。
-
Parallel Scavenge同樣是作用在新生代且是多線程,不過它的設計目標是達到一個可控制的吞吐量。
-
Parallel Old是Parallel Scavenge收集器的老年代版本,我們可以把它和Parallel Scavenge搭配一起使用。
-
CMS是一種以最短停頓時間為目標的多線程收集器,下文我會介紹CMS實現最短停頓的原理。
-
G1收集器可以說是CMS的升級版。
我們可以根據業務實際情況來為各個年代搭配不同的垃圾回收器,以下的垃圾回收器如果有線連接,說明它們之間可以搭配使用。
2.4 CMS原理
面試官:你說的CMS為什么有較短的停頓?
CMS采用了標記-清除算法,整個運作過程分為了初始標記、并發標記、重新標記、并發清除四個階段。
其中初始標記、重新標記的停頓時間是比較短的,而耗時最長的并發標記、并發清除能夠和用戶線程一起并發工作不需要停頓,可以說CMS只需要造成初始標記、重新標記帶來的短時間停頓。
2.5 CMS的缺點
面試官:那它有什么缺點?
- CMS是多線程的,在垃圾回收時會占用一部分線程,可能會使系統變得相對較慢。
- CMS并發清理時用戶線程還在運行著,也就是說還會有新的垃圾不斷產生,這些垃圾被稱為浮動垃圾。因為浮動垃圾產生在標記階段后,很明顯CMS本次收集是無法處理這些浮動垃圾的,只能等到下一次GC回收。
- CMS采用標記-清除算法,標記-清除算法的缺點是會產生空間碎片,有可能造成大對象找不到足夠的連續空間而發生OOM的情況。
2.6 G1垃圾回收器
面試官:你說G1是CMS的升級版,為什么?
G1垃圾回收器設計之初被賦予的使命是未來可以替換掉JDK1.5中發布的CMS垃圾回收器。所以大家可想而知,CMS垃圾回收器的優點G1垃圾回收器都有,另外G1垃圾回收器也避免了CMS的一些不足。
- G1采用的垃圾回收算法是標記-整理算法,避免了CMS采用標記-清除可能產生的空間碎片。
- 其他收集器在新生代、老年代分別采用不同收集器進行配合,而G1垃圾回收器可以不需要其他收集器配合就能獨立管理整個GC。
3. 垃圾回收算法
面試官:垃圾回收算法都有什么?
垃圾回收算法一共有四種,其中最基礎的垃圾回收算法是標記-清除算法,其他算法其實都是對標記-清除算法的優化而產生的,我們繼續往下看。
(1)標記-清除算法。
標記-清除算法顧名思義分為標記和清除兩個階段,首先標記出所有可回收的對象,標記完成后統一進行清除。但該算法有一個缺點,被標記和未標記的對象都是分散存儲在內存中的,當清除標記對象后會出現空間碎片的情況,如下圖:
(2)復制算法。
復制算法把內存劃分為容量相等的兩塊,每次只使用一塊,當這一塊內存不足時就將存活的對象復制到另一塊中,同時清除當前塊的內存空間。這種算法實現簡單且運行高效,也不會產生空間碎片的情況,因為新生代的GC是比較頻繁的,所以復制算法也廣泛用于新生代的垃圾回收。但缺點很明顯是浪費了50%的內存空間。
(3)標記-整理算法。
標記-整理算法是對標記-清除算法的優化。該算法在內存到達一定量后,會把所有已標記的垃圾對象都向一端里移動,然后以存活對象所在的一端為邊界,清除邊界內所有內存,避免了標記-清除算法可能產生的空間碎片。
(4)分代收集算法。
一般實際業務系統都是采用分代收集算法。分代顧名思義把JVM內存拆分,分為了新生代、老年代,對不同年代的垃圾回收采用不同的垃圾回收算法來確保回收效率。
大家可以看下自己公司的JDK使用了什么垃圾回收器,加深下對本篇的理解。
# 打印JVM啟動時的命令行標志
java -XX:+PrintCommandLineFlags -version
3.1 優化復制算法
面試官:復制算法可以怎么優化嗎?
復制算法把內存劃分為容量相等的兩塊,也就是按1:1分配內存,但這也浪費了50%空間。
可以把內存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次只使用Eden空間和其中一塊Survivor空間,而另一塊Survivor空間用來保存回收時還存活的對象。這樣就只浪費了其中一塊Survivor空間的內存。
?覆蓋Java程序員所需掌握的Java核心知識、面試重點,本博客收錄在我開源的《Java學習面試指南》中,會一直完善下去,希望收到大家的 ? Star ?支持,這是我創作的最大動力: https://github.com/hdgaadd/JavaGetOffer
未完待續。。。
創作不易,不妨點贊、收藏、關注支持一下,各位的支持就是我創作的最大動力??