#jdk1.7.0_79
眾所周知, Java是一門不用程式設計師手動管理內存的語言,全靠JVM自動管理內存,既然是自動管理,那必然有一個垃圾內存的回收機製或者回收算法。本文將介紹幾種常見的垃圾回收(下文簡稱GC)演算法。
在Java堆疊上指派一個記憶體給實例物件時,此時在虛擬機器堆疊上引用型變數就會存放這個實例物件的起始位址。
Object obj = new Object();
現在如果我們將變數賦值為null。
obj = null;
此時可以看到Java堆上的實例物件無法再引用它,那麼它就是被GC的對象,我們稱之為物件「已死”。那虛擬機器棧上的obj變數呢?上文《JVM入門-運行時資料區》提到過,虛擬機棧是線程獨佔的,也就是說隨著線程初始而初始,消亡而消亡,當線程被銷毀後,虛擬機棧上的內存自然會被回收,也就是說虛擬機器堆疊上的這塊記憶體空間不在虛擬機器GC範圍。下圖展示了垃圾回收的記憶體範圍:
1.物件是否「已死」演算法-引用計數器演算法
# 物件中新增一個引用計數器,如果引用計數器為0則表示沒有其它地方在引用它。如果有一個地方引用就 1,引用失效時就-1。看似搞笑且簡單的演算法,其實在大部分Java虛擬機器中並沒有採用這種演算法,因為它會帶來一個致命的問題──物件循環引用。物件A指向B,物件B反過來指向A,此時它們的引用計數器都不為0,但它們兩個實際上已經沒有意義因為沒有任何地方指向它們。所以又引出了下面的演算法。
2.物件是否「已死」演算法-可達性分析演算法
這種演算法可以有效地避免物件循環引用的情況,整個物件實例以一個樹呈現,根節點是一個稱為「GC Roots」的對象,從這個物件開始向下搜尋並作標記,遍歷完這棵樹過後,未被標記的物件就會判斷“已死”,即為可被回收的物件。
GC演算法
1.標記-清除演算法
等待回收物件的「標記」過程在上文已經提到過,如果在被標記後直接對物件進行清除,會帶來另一個新的問題——記憶體碎片化。如果下次有比較大的物件實例需要在堆上分配較大的記憶體空間時,可能會出現無法找到足夠的連續記憶體而不得不再次觸發垃圾回收。
2.複製演算法(Java堆中新生代的垃圾回收演算法)
此GC演算法實際上解決了標記-清除演算法帶來的「記憶體碎片化」問題。首先還是先標記處待回收內存和不用回收的內存,下一步將不用回收的內存複製到新的內存區域,這樣舊的內存區域就可以全部回收,而新的內存區域則是連續的。它的缺點就是會損失掉部分系統內存,因為你總是要騰出一部分內存來複製。
在上文《JVM入門-運行時資料區》提到在Java堆中被分成了新生代和老年代,這樣的劃分是方便GC。 Java堆中的新生代就使用了GC複製演算法。在新生代中又分為了三個區域:Eden 空間、To Survivor空間、From Survivor空間,from survivor 和 to survivor大小相同,且保證一個為empty.。不妨將注意力回到這張圖的左邊新生代部分,:
新的物件實例被創建的時候通常在Eden空間,發生在Eden空間上的GC稱為Minor GC,當新生代發生一次GC後,會將Eden和其中一個Survivor空間的記憶體複製到另一個Survivor中,如果重複幾次有物件一直存活,此時記憶體物件將會被移至老年代。可以看到新生代中Eden佔了大部分,而兩個Survivor實際上佔了很小一部分。這是因為大部分的物件在創建後很快就會被GC(這裡也許運用了是二八原則)。
3.标记-压缩算法(或称为标记-整理算法,Java堆中老年代的垃圾回收算法)
对于新生代,大部分对象都不会存活,所以在新生代中使用复制算法较为高效,而对于老年代来讲,大部分对象可能会继续存活下去,如果此时还是利用复制算法,效率则会降低。标记-压缩算法首先还是“标记”,标记过后,将不用回收的内存对象压缩到内存一端,此时即可直接清除边界处的内存,这样就能避免复制算法带来的效率问题,同时也能避免内存碎片化的问题。老年代的垃圾回收称为“Major GC”。
不积跬步,无以至千里;不积小流,无以成江海。
1.JVM的堆栈
栈:在jvm中栈用来存储一些对象的引用、局部变量以及计算过程的中间数据,在方法退出后那么这些变量也会被销毁。它的存储比堆快得多,只比CPU里的寄存器慢
堆:用来存储程序中的一些对象,比如你用new关键字创建的对象,它就会被存储在堆内存中,但是这个对象在堆内存中的首地址会存储在栈中。
栈内存在JVM中默认是1M,可以通过下面的参数进行设置
-Xss
1
最小堆内存在JVM中默认物理内存的64分之1,最大堆内存在JVM中默认物理内存4分之一,且建议最大堆内存不大于4G,并且设置-Xms=-Xmx避免每次GC后,调整堆的大小,减少系统内存分配开销
-Xms -Xmx
1
2
3
在jvm的堆内存中有三个区域:
1.年轻代:用于存放新产生的对象。
2.老年代:用于存放被长期引用的对象。
3.持久带:用于存放Class,method元信息。
如图:
一.年轻代
年轻代中包含两个区:Eden 和survivor,并且用于存储新产生的对象,其中有两个survivor区如图:
可以使用参数配置年轻代的大小,如果配置它为100M那么就相当于2*survivor+Eden = 100M
-Xmn
1
可以配置Eden 和survivor区的大小,这里配置的是比值,jvm中默认为8,意思就是Eden区的内存比上survivor的内存等于8,如果年轻代的Xmn配置的100M,那么Eden就会被分配80M内存,每个survivor分配10M内存
-XX:SurvivorRatio
1
还可以配置年轻代和老年代的比值,这里需要注意:老年代的内存就是通过这个比值设置,jvm没有给你直接设置老年代内存大小的参数;如果整个堆内存设为100M并且在这里设置年轻代和老年代的比值为7,如果持久代占用了10M,那么100M-10M=90M这里的90M就是老年代和年轻代的内存总和,且年轻代占用(90/(7+1)*7)的内存,老年代就占用(90/(7+1)*1)的内存。
-XX:NewRatio
1
二.老年代
年轻代在垃圾回收多次都没有被GC回收的时候就会被放到老年代,以及一些大的对象(比如缓存,这里的缓存是弱引用),这些大对象可以不进入年轻代就直接进入老年代(1.防止新生代有大量剩余的空间,而大对象创建导致提前发生GC;2.防止在eden区和survivor区的大对象复制造成性能问题),这个可以通过如下参数设置,表示单个对象超过了这个值就会直接到老年带(默认为0):
-XX:PretenureSizeThreshold
1
并且大的数组对象也会直接放到老年代,比如array和arrayList(底层用数组实现),因为数组需要连续的空间存储数据。
三.持久代
持久代用来存储class,method元信息,大小配置和项目规模,类和方法的数量有关,一般配置128M就够了,设置原则是预留30%空间,它可以通过如下参数进行大小配置:
-XX: PermSize -XX: MaxPermSize
1
2
持久代也可能会被GC回收,如果持久代理的常量池没有被引用以及一些无用的类信息和类的Class对象也会被回收。
相关文章:
相關影片:
以上是JVM垃圾回收演算法與jvm的堆記憶體中的三個區域的詳細內容。更多資訊請關注PHP中文網其他相關文章!