Java中GC原理及GC日志剖析
一.概述
學習Java的我們都知道垃圾收集(GC),大部分人把這項技術當作是Java語言的伴生產物。事實上,GC的歷史比Java久遠,1960年誕生于MIT的Lisp是第一門真正使用內存動態分配和垃圾收集技術的語言。那我們今天就研究下垃圾收集原理。
二.對象已死嗎?
Java的垃圾回收主要是對堆內存的回收,里面存放著Java幾乎所有的對象實例,垃圾回收之前是要確定哪些還“存活”,哪些已經“死去”。
1.引用計數器法
給對象添加一個引用計數器,每當有地方對他進行引用時計數器值➕1;當引用失效時,計數器值就➖1,任何時候計數器值為0的時候表示對象不可能在使用的。
2.可達性分析算法
通過一系列稱為“GC Roots”的對象作為起點,從這些節點往下搜索,搜索所走過的路徑稱為“引用鏈”,當一個對象到“GC Roots”沒有任何引用鏈相連時,則證明對象是不可用的。
代碼示例:
GC日志如下:
我們很明顯的看到GC日志中6092K->456K,意味著虛擬機并沒有因為這兩個對象互相引用而不回收他們,所以Java虛擬機使用的是可達性分析算法標記的。
其實即使被可達性分析算法標記的不可達對象也不是一定會被回收的,虛擬機會對這些對象進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法或者finalize()已經被虛擬機調用過,虛擬機將這兩種情況視為“沒有必要執行”。如果對象被判定有必要執行,finalize()方法是并在finalize()中與“GC Roots”建立關聯,則此對象不會被回收了。
三.垃圾回收算法
我們知道了虛擬機怎么標記一個對象是否可用,那他怎么進行回收的呢?其實堆內存可以分為新生代和老年代,新生代又被劃分為一個Eden和兩個Survivor區域,他們的比例為8:1:1,不同的垃圾收集器廠商對這兩個區域給出了不同的算法。
1.新生代——復制算法
新生代對象的特點就是,大部分對象在一次GC中會被回收掉,所以使用的是復制算法:新生代每次創建對象的時候只會使用一個Eden和其中的一塊Survivor,在垃圾回收時將存活的對象復制到另外一塊Survivor區域,最后清理掉Eden和剛才的Survivor區域。
2.老年代——標記-整理算法
老年代一般保存的是一些大對象,或者不被經?;厥盏膶ο?,根據特點使用的標記-整理算法:如同名字一樣,算法分為“標記”和“整理”兩個階段:首先先標記出所有需要回收的對象,在標記完成后統一回收所有被標記的對象進行整理,將被標記的對象都向一端移動,然后直接清理掉邊界以外的內存。
四.HotSpot算法實現
上面說了我們怎么標記對象“死亡”和怎么進行垃圾回收的,但在HotSpot虛擬機在實現這些算法上是必須對算法的執行效率進行考量的。
1.安全點
在可達性分析中對執行的時間的敏感體現在GC停頓上,其意思是在整個分析的過程中看起來就像被凍結在某一個時間點上的,不可以出現分析的過程中引用關系在不斷變化,如果這點得不到保證則分析的結果的準確性就得不到保障。這點是導致在GC進行時需要停頓所有的Java執行線程。
當執行系統停頓下來后,虛擬機并不需要全部上下文和全局所有的位置,虛擬機通過一個OopMap的數據結構在類加載的時候將對象的偏移量數據信息記錄下來,所以GC掃描是直接得到這些信息的。其實這些通過指令被加入進行記載對象信息的OopMap位置也叫做安全點,程序執行時并非所有點都可以停下來開始GC的,只有在到達安全點才能停頓。安全點機制程序執行中,在不太長的時間內會遇到可進入GC的安全點。在實際中會遇到在GC時有線程不再執行,例如線程被掛起了。這是我們需要安全區域去解決。
2.安全區域
安全區域是指在一段代碼片段中,引用關系不會發生變化。在這個區域中的任意地方開始GC都是安全的。在代碼執行到安全區域時,首先表示這直接進去安全區域,這樣虛擬機在這段時間GC時就不用管那些標記為安全區域的線程了。當離開安全區域時首先得判斷GC分析是否完成,沒完成則需要等待。
五.理解GC日志
這是上圖打印的GC日志
[GC (System.gc()) [PSYoungGen: 6092K->448K(38400K)] 6092K->456K(125952K), 0.0051702 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
其中,PSYoungGen表示的是新生代GC不同垃圾收集器新生代名稱不一樣,6092K->448K(38400K)表示新生代大小的變化,6092K->456K(125952K)表示堆內存的大小變化,后面表示用時。
[Full GC (System.gc()) [PSYoungGen: 448K->0K(38400K)] [ParOldGen: 8K->378K(87552K)] 456K->378K(125952K), [Metaspace: 3050K->3050K(1056768K)], 0.0056045 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
這里表示發生在老年代的GC(Major GC/Full GC) 它只是為伴隨一次的新生代的GC(Minor GC),448K->0K(38400K)表示新生代內存變化,8K->378K(87552K) 表示老年代GC變化,456K->378K(125952K)表示GC前后堆內存的變化。
【本文是51CTO專欄機構“AiChinaTech”的原創文章,微信公眾號( id: tech-AI)”】