以前看過很多次關於垃圾回收相關的文章,都只是看過就忘記了,沒有好好的整理一下,發現寫文章可以強化自己的記憶。
java與C,c++有很大的不同就是java語言開發者不需要關注內存信息,不會顯式的直接操作內存,而是通過jvm虛擬機來實現。
java虛擬機運行的時候內存分配圖如下圖:
jvm虛擬機棧:一個是線程獨有的,每次啟動一個線程,就創建一個jvm虛擬機棧,線程退出的時候就銷毀。這里面主要保存線程本地變量名和局部變量值。
本地方法棧: 調用本地jni方法的時候而創建的。這里分配的jvm之外的內存空間。方法調用結束之后銷毀。
pc寄存器 : 這個保存線程當前執行的字節碼指令
堆:主要保存創建的對象。
方法區:保存class相關的信息。主要是class的一個內存結構信息
常量池:方法區的一部分,主要保存class內存結構中常量值 例如String值,public static final 類型的值
我們這里說的垃圾回收,主要是java虛擬機對堆內存區域的回收。
1 首先的問題是:jvm如何知道那些對象需要回收 ?
目前有兩種算法
- 引用計數法
每個對象上都有一個引用計數,對象每被引用一次,引用計數器就+1,對象引用被釋放,引用計數器-1,直到對象的引用計數為0,對象就標識可以回收
這個可以用數據算法中的圖形表示,對象A-對象B-對象C 都有引用,所以不會被回收,對象B由於沒有被引用,沒有路徑可以達到對象B,對象B的引用計數就就是0,對象B就會被回收。
但是這個算法有明顯的缺陷,對於循環引用的情況下,循環引用的對象就不會被回收。例如下圖:對象A,對象B 循環引用,沒有其他的對象引用A和B,則A和B 都不會被回收。
- root搜索算法
這種算法目前定義了幾個root,也就是這幾個對象是jvm虛擬機不會被回收的對象,所以這些對象引用的對象都是在使用中的對象,這些對象未使用的對象就是即將要被回收的對象。簡單就是說:如果對象能夠達到root,就不會被回收,如果對象不能夠達到root,就會被回收。
如下圖:對象D訪問不到根對象,所以就會被回收
以下對象會被認為是root對象:
- 被啟動類(bootstrap加載器)加載的類和創建的對象
- jvm運行時方法區類靜態變量(static)引用的對象
- jvm運行時方法去常量池引用的對象
- jvm當前運行線程中的虛擬機棧變量表引用的對象
- 本地方法棧中(jni)引用的對象
由於這種算法即使存在互相引用的對象,但如果這兩個對象無法訪問到根對象,還是會被回收。如下圖:對象C和對象D互相引用,但是由於無法訪問根,所以會被回收。
jvm在確定是否回收的對象的時候采用的是root搜索算法來實現。
在root搜索算法的里面,我們說的引用這里都指定的是強引用關系。所謂強引用關系,就是通過用new 方式創建的對象,並且顯示關聯的對象
Object obj = new Object();
以上就是代表的是強引用關系,變量obj 強引用了 Object的一個對象。
java里面有四種應用關系,從強到弱分別為:
Strong Reference(強引用) –>Weak Reference (弱引用) -> Soft Reference(軟引用) – > Phantom Reference(引用)
Strong Reference : 只有在引用對象root不可達的情況下才會標識為可回收,垃圾回收才可能進行回收
Weak Reference :即使在root算法中 其引用的對象root可達到,但是如果jvm堆內存 不夠的時候,還是會被回收。
Soft Reference : 無論其引用的對象是否root可達,在響應內存需要時,由垃圾回收判斷是否需要回收。
Phantom Reference :在回收器確定其指示對象可另外回收之后,被加入垃圾回收隊列.
下面可以看一個測試
public class ReferenceTest { public static final Map<Integer, Reference> map = new HashMap<Integer, Reference>(); public static void main(String[] args) { for (int i = 0; i < 1000; i++) { map.put(i, new WeakReference(new ReferenceObject(i))); } int i = 0; for (Reference r : map.values()) { if (r.get() == null) { i++; } } System.out.println("被回收的對象數:" + i); } static class ReferenceObject { private int i; private byte[] b; public ReferenceObject(int i) { this.i = i; b = new byte[1024 *10]; } } }
這里創建大約1000個 10K的 Weak Reference 對象,最后打印的結果是:被回收的對象數:767,這里ReferenceObject如果設置為1K的話,最后的打印結果是0
這個例子並不嚴謹,但是卻說明了被Weak Reference的對象在一定的時候會被jvm回收,但是強引用就不會出現這種狀態。