1. 程式人生 > >JVM(二)—— 垃圾回收

JVM(二)—— 垃圾回收

class 隨著 策略 兩種 oid 獨立 內存管理 最大 就會

垃圾回收

垃圾回收主要解決三個問題(回收哪些Which,什麽時候回收WHEN,如何回收HOW)

一、回收哪些

這三個問題,最主要的還是第一個,Which回收哪些,評斷回收還是不回收的標準是看對象是否被引用

引用分為四種:

  • 強引用:一個對象被一個引用所指向。絕對不會被JVM回收的,即使內存不過用
  • 軟引用:只有在堆內存不夠用的時候才會被回收。使用SoftReference實現
  • 弱引用:弱引用相對於軟引用,引用級別更低。只要垃圾回收器啟動了回收,就會被回收掉。絕WeakReference實現
  • 虛引用:虛引用的主要作用是跟蹤對象被垃圾回收的狀態。虛引用對對象本身沒有太大影響,必須和引用隊列(ReferenceQueue)聯合使用

1、引用計數法

如果兩個對象相互引用,就不會被回收,當然,GC並沒有采用這種算法

2、根搜索算法

從根開始,沿著整個對象圖上的每條鏈接,確定可達的對象,如果對象不可達,則作為垃圾收集
是對(1)的改進,根對象到達某一對象不可達,GC就會對其回收。

public static void main(String[] args) {
        Node n1 = new Node();
        Node n2 = new Node();
        Node n3 = new Node();
        n1.next = n2;
        n3 = n2;
        n2 = null
; }

技術分享圖片

二、何時回收,如何回收

這就需要垃圾收集算法來解決,但講垃圾回收算法之前需要明白一個分代的概念

1、分代的策略

絕大多數的對象不會被長時間引用,這些對象在其Young期間就會被回收
很老的對象和很新的對象之間很少存在相互引用

技術分享圖片

Young代

大部分垃圾回收器對Young代都采用復制算法,為什麽?因為Young代處於可達的對象數量少,所以復制成本不大
Young代由一個Eden區和2個Survivor區構成。絕大多數對象先分配到Eden區中,Survivor區中的對象都至少經歷過一次垃圾回收
為什麽要有2個Survivor區,是因為其中一個Survivor是空的,來存放Young代的對象。最後會清空Eden區和第一個Survivor區
思考:Survivor的大小設置的變化會產生什麽影響

Old代

Young代的對象經過多次垃圾回收依然沒有被回收,就會被轉移到Old代
隨著時間流逝,Old代的對象會越來越多,因此Old代的空間要與Young代的空間大
Old代的垃圾回收的兩個特征:Old代垃圾回收的執行頻率不需要太高,因為死的少。每次回收需要更長的時間來完成(如何理解,因為對象多吧)
垃圾回收通常會采用標記壓縮算法。因為對象不會很快死亡,也不會大量產生內存碎片

Permanent代

主要用於裝載Class、方法等信息
垃圾回收機制通常不會回收這一代的對象。
服務器程序通常會加載很多類,需要加大Permanent代內存。
OutOfMemoryError:PermGen space錯誤

2、垃圾回收算法

標記-清除算法

標記所有需要回收的對象,標記完成後,統一回收所有被標記的對象,
不足之處,標記和清除的效率不高,標記清除後會產生大量不連續的內存碎片

復制算法

為了解決效率問題,將內存分為大小相等的兩塊,每次使用一塊,當這一塊內存使用完了之後,將存活的對象放到另一塊內存中,然後對原先那一半內存進行回收。實現簡單,運行高效,不過代價是將內存縮小一半,代價過高。

標記-整理算法

是對標記清除算法的改進,進行標記好了之後,將存活的對象都向一邊移動,然後直接清理掉端邊界以外的內存

分代收集算法

商業虛擬機都采用“分代收集”算法,根據對象的存活周期將內存分為幾塊,一般是將java堆分成新生代和老年代,如果新生代有很少的存活對象,就用復制算法,老年代有很多存活對象,就用標記-清除或標記-整理算法

HotSpot虛擬機下的垃圾收集器

由於內存中的對象,是按存活周期存放在不同的內存塊中的,所以,我們選擇不同的算法來針對不同的內存塊進行垃圾收集。從而,對於,不同的內存塊,我們需要有不同的垃圾收集器。

新生代的垃圾收集器有:Serial收集器ParNew收集器Parallel Scavenge收集器

老年代的垃圾收集器有:Serial Old收集器Parallel Old收集器CMS收集器G1收集器

Serial收集器/Serial Old收集器

串行,是單線程的,使用“復制”算法。當它工作時,必須暫停其它所有工作線程。特點:簡單而高效。一般用於Client模式的JVM中
Serial Old是老年代的單線程收集器,使用標記-整理算法。

ParNew收集器

ParNew收集器,是Serial收集器的多線程版。是運行在Server模式下的虛擬機中首選的新生代收集器。除了Serial收集器外,目前只有它能與CMS收集器配合工作。

Parallel Scavenge收集器

吞吐量優先收集器,吞吐量=程序運行時間/(JVM執行回收時間+程序運行時間),是server模式JVM的默認配置

Parallel Old收集器

老年代的多線程收集器,使用標記-整理算法,吞吐量優先,適合於Parallel Scavenge搭配使用

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器,使用“標記-清除”算法。
回收線程數=(CPU核心數+3)/4
CMS收集器分4個步驟進行垃圾收集工作:
1、初始標記 2、並發標記 3、重新標記 4、並發清除
其中“初始標記”、“重新標記”是需要暫停其它所有工作線程的。

G1收集器

G1(Garbage First)收集器,基於“標記-整理”算法,可以非常精確地控制停頓。
可以不與其他收集器搭配,獨立收集新生代和老年代。

三、內存管理技巧

1、盡量使用直接量:String str = "hello"

2、使用StringBuilder和StringBuffer進行字符串連接

3、盡早釋放無用對象的引用

Object obj = new Object();obj = null;這行代碼並不能發揮C++中的delete和free作用,其作用僅僅是斷開obj 與new Object()的關聯,new Object所占用的內存並沒有釋放掉。以此來誘發GC對其進行回收。如果是在方法中,其實要考慮兩種情況,多數情況下不需要這麽寫,對象會隨著因為方法調用的結束而結束。但如果obj =null之後,還有耗時耗內存的操作的話,就需要這樣寫。

四、設置Java虛擬機內存的一些參數

  • -Xmx 設置堆內存的最大容量
  • -Xms 設置堆內訓初始容量
  • -XX:NewSize = size 設置Young代內存的默認容量
  • -XX:SurvivorRatio = 8 設置Young代中eden/survivor的比例
  • -XX:MaxNewSize = size 設置Young代內存的最大容量
  • -XX:PermSize = size 設置永久代的默認容量
  • -XX:MaxPermSize = size 設置永久代內存的最大容量

JVM(二)—— 垃圾回收