Java垃圾回收機制(轉)

Java內存回收機制

 本文轉自https://www.cnblogs.com/prophet-it/p/6498275.html

1.java的內存

java的內存結構分爲javascript

  • 堆 (是gc的主要區域) 線程共享,主要是用於分配實例對象和數組
  • 棧 線程私有,它的生命週期和線程相同,又分紅 虛擬機棧和本地方法棧,只有它會報 StackOverFlowError,棧深度超標
  • 方法區 線程共享 用於儲存被虛擬機加載的類的信息,靜態變量 常量和編譯後的.class字節碼
  • 程序計數器 線程私有,線程之間不相互影響,獨立存取;
    以上部分,線程私有是不會發生gc.而且他們是隨線程生隨線程滅,即程序計數器 本地方法棧和虛擬機棧

 

2.GC回收機制--判斷是否能夠gc

    • 引用計數算法
      原理:經過一個計數器對對象進行計數,對象被引用時+1,引用失效時-1;當計數爲0時則說明能夠被回收;
      缺點:很難解決對象的相互循環引用問題
    • 可達性分析算法
      Java虛擬機所採用的算法;
      原理:經過一些列稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。
      那麼哪些對象能夠被稱爲gc roots呢----虛擬機棧(棧中的本地變量列表)/方法區靜態屬性/方法區常量引用/本地方法棧中JNI 所引用的的對象都是能夠做爲 gc roots的
 
 

深刻理解分代回收算法 Survivor(倖存者) Eden (谷歌翻譯爲伊甸園)

  • 複製算法中內存劃分其實並非按照1:1來劃分老年代和新生代,,而是按照8:1:1分一個大的Eden區和兩個小的survivor的空間
  • 爲何須要2個Survivor區 新生代通常經歷15次Gc就能夠移到老年代.當第一次gc時,咱們能夠把Eden的存活對象放入Survivor A空間,第二次Gc時,Survivor A也要使用複製算法,存活對象放到Survivor B上,第三次gc時,又將Survivor B對象複製到Survivor A上如此循環往復;
  • 爲何Eden這麼大,由於新生代中存活的對象,須要轉移的Survivor 的對象很少,算是緩解了複製算法的缺點;

4.GC回收機制--gc的執行機制

    • Scavenge GC
      當新對象生成而且在Eden申請空間失敗時就會觸發Scavenge GC;Eden區的gc會比較頻繁
    • Full GC
      是對整個堆進行清理,要比Scavenge GC要慢,什麼狀況要進行Full GC呢,以下四種:
      持久代被寫滿
      System.gc調用
      老年代被寫滿
      上一次GC以後Heap的各域分配策略動態變化
      • Java虛擬機內存原型

        寄存器:咱們在程序沒法控制 
        棧:存放基本類型的數據和對象的引用,但對象自己不存放在棧中,而是堆中 
        存取速度比堆塊,僅次於寄存器,棧數據能夠共享,棧的數據大小與生存期必須是肯定的,缺少靈活性。 
        堆:存放new產生的數據 
        能夠動態分配內存大小,生存期也沒必要事先告訴編譯器,由於它在運行時動態分配內存,Java的垃圾收集器會自動收走這些再也不使用的數據,但缺點是,因爲在運行時分配內存,存取速度較慢 
        靜態域:存放在對象中用static定義的靜態成員 
        常量池:存放常量(利用final關鍵字修飾的) 
        非RAM存儲:硬盤等永久存儲空間html

        Java引用的種類

        >對象在內存中狀態

        對於JVM的垃圾回收機制來講,若是一個對象,沒有一個引用指向它,那麼它就被認爲是一個垃圾。那該對象就會回收。能夠把JVM內存中對象引用理解成一種有向圖,把引用變量、對象都當成有向圖的頂點,將引用關係當成圖的有向邊(注意:有向邊老是從引用變量指向被引用的Java對象) 
        一、可達狀態 
        當一個對象被建立後,有一個或一個以上的引用變量引用它,則這個對象在程序中處於可達狀態,程序能夠經過引用變量來調用該對象的方法和屬性。 
        二、可恢復狀態 
        若是程序中某個對象再也不有任何引用變量引用它,它就進入了可恢復狀態,在這個狀態下,系統的垃圾回收機制準備回收該對象所佔用的內存空間,在回收該對象以前,系統會調用全部對象的finalize方法進行資源的清理,若是系統在調用finalize方法從新讓一個引用變量引用該對象,則這個對象會再次變爲激活狀態,不然該 對象狀進入不可達狀態。 
        三、不可達狀態 
        當對象與全部引用變量的關聯都被切繼,且系統已經調用全部對象的finalize方法依然沒有該對象變成可達狀態,那這個對象將永久性地失去引用,最後變成不可達狀態,只有當一個對象處於不可達狀態時統纔會真正回收該對象所佔有的資源。java

        *對象的狀態轉換圖以下: 
        這裏寫圖片描述程序員

        >強引用

        強引用是Java編程中使用普遍的引用類型,被強引用所引用的Java對象毫不會被垃圾回收,當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具備強引用的對象來解決內存不足的問題,所以強引用是形成Java內存泄漏的主要緣由之一 
        *代碼示例算法

        class Person
        {
            String name;
            int age; public Person(String name,int age) { this.name=name; this.age=age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class ReferenceTest { public static void main(String[] args) { //建立一個長度爲10000的強引用數組,來保存10000個Person對象 Person[] person=new Person[10000]; //依次初始化 for(int i=0;i<person.length;i++) { person[i]=new Person("名字"+i,(i+1)*2%100); } System.out.println(person[1]); System.out.println(person[3]); //通知系統進行垃圾回收 System.gc(); System.runFinalization(); System.out.println(person[1]); System.out.println(person[3]); } } 
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35

        運行結果(結果說明內存仍是足夠的) 
        這裏寫圖片描述 
        咱們來把修改java虛擬機內存,把堆內存減小到2m 
        (操做:程序右鍵選屬性->run/debug settings->選中應用程序->編輯->Arguments->VM arguments輸入框輸入 -Xmx2m -Xms2m ) 
        這裏寫圖片描述 
        再運行(程序由於內存不足而停止) 
        這裏寫圖片描述數據庫

        >軟引用

        軟引用經過SoftReference類來實現,當系統內存空間足夠,軟引用的對象不會被系統回收,程序也可使用該對象,當系統內存不足時,系統將會回收 
        *代碼示例編程

        // 建立一個長度爲10000的弱引用數組,來保存10000個Person對象 SoftReference<Person>[] person = new SoftReference[10000]; // 依次初始化 for (int i = 0; i < person.length; i++) { person[i] = new SoftReference<Person>(new Person("名字" + i, (i + 1) * 2 % 100)); } 
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7

        運行結果(結果說明內存足夠的) 
        這裏寫圖片描述 
        修改java虛擬機內存,把堆內存減小到2m,再運行 
        這裏寫圖片描述 
        從運行結果能夠看出,內存不足時,垃圾回收機制會回收SoftReference引用的對象數組

        >弱引用

        弱引用與軟引用有點類似,區別是弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它所管轄的內存區域的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。不過,因爲垃圾回收器是一個優先級很低的線程,所以不必定會很快發現那些只具備弱引用的對象。 
        弱引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。 
        *代碼示例緩存

        //建立一個字符串對象 String str=new String("瘋狂Java講義"); //建立一個弱引用,讓該弱引用引用到「瘋狂Java講義」字符串對象 WeakReference<String> wr=new WeakReference<String>(str); //切斷str引用變量和「瘋狂Java講義」字符串對象之間的引用關係 str=null; //取出弱引用所引用的對象 System.out.println(wr.get()); //強制垃圾回收 System.gc(); System.runFinalization(); //再次取出弱引用所引用的對象 System.out.println(wr.get()); 
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16

        內存分配圖 
        這裏寫圖片描述 
        運行結果 
        這裏寫圖片描述 
        從運行結果看出,當系統垃圾回收機制啓動後,弱引用的對象就會被清除掉。null表面該對象已經被清除了安全

        >虛引用

        虛引用經過PhantomReference類實現,相似沒有引用,主要做用是跟蹤對象被垃圾回收的狀態,程序能夠經過檢查與虛引用關聯的引用隊列中是否已經包含指定的虛引用,從而瞭解虛引用所引用的對象是否即將被回收,虛引用不能單獨使用,必需要和引用隊列(ReferenceQueue)聯合使用 
        *代碼示例

        //建立一個字符串對象 String str=new String("這是虛引用"); //建立一個引用隊列 ReferenceQueue<String> rq=new ReferenceQueue<String>(); //建立一個虛引用,讓該虛引用引用到「這是虛引用」字符串對象 PhantomReference<String> pr=new PhantomReference<String>(str,rq); //切斷str引用與"這是虛引用"字符串之間的引用 str=null; //取出虛引用所引用的對象 System.out.println(pr.get()); //強制垃圾回收 System.gc(); System.runFinalization(); //取出引用隊列中最早進入隊列中引用與pr進行比較 System.out.println(rq.poll()==pr);
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17

        運行結果 
        這裏寫圖片描述 
        從運行結果能夠看出,在未強制進行垃圾回收,程序輸出null,說明系統沒法經過虛引用訪問被引用的對象,當程序強制回收垃圾後,虛引用引用的對象被回收,而後該引用會添加到關聯的引用隊列中,因此輸出true,因此說程序能夠經過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收


        Java的內存泄漏

        無用對象(再也不使用的對象)持續佔有內存或無用對象的內存得不到及時釋放,從而形成的內存空間的浪費,就是內存泄漏

        一、靜態變量引發內存泄露: 
        根據分代回收機制(後面有講),JVM會將程序中obj引用變量存在Permanent代裏,這致使Object對象一直有效,從而使obj引用的Object得不到回收 
        例:

        class Person { static Object obj=new Object(); }
        • 1
        • 2
        • 3
        • 4

        二、當集合裏面的對象屬性被修改後,再調用remove()方法時不起做用。 
        例:

        public static void main(String[] args) { Set<Person> set = new HashSet<Person>(); Person p1 = new Person("唐僧","pwd1",25); Person p2 = new Person("孫悟空","pwd2",26); Person p3 = new Person("豬八戒","pwd3",27); set.add(p1); set.add(p2); set.add(p3); System.out.println("總共有:"+set.size()+" 個元素!"); //結果:總共有:3 個元素! p3.setAge(2); //修改p3的年齡,此時p3元素對應的hashcode值發生改變 set.remove(p3); //此時remove不掉,形成內存泄漏 set.add(p3); //從新添加,竟然添加成功 System.out.println("總共有:"+set.size()+" 個元素!"); //結果:總共有:4 個元素! for (Person person : set) { System.out.println(person); } }
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25

        三、監聽器 
        在java 編程中,咱們都須要和監聽器打交道,一般一個應用當中會用到不少監聽器,咱們會調用一個控件的諸如addXXXListener()等方法來增長監聽器,但每每在釋放對象的時候卻沒有記住去刪除這些監聽器,從而增長了內存泄漏的機會。

        四、各類鏈接 
        好比數據庫鏈接(dataSourse.getConnection()),網絡鏈接(socket)和io鏈接,除非其顯式的調用了其close()方法將其鏈接關閉,不然是不會自動被GC 回收的。對於Resultset 和Statement 對象能夠不進行顯式回收,但Connection 必定要顯式回收,由於Connection 在任什麼時候候都沒法自動回收,而Connection一旦回收,Resultset 和Statement 對象就會當即爲NULL。可是若是使用鏈接池,狀況就不同了,除了要顯式地關閉鏈接,還必須顯式地關閉Resultset Statement 對象(關閉其中一個,另一個也會關閉),不然就會形成大量的Statement 對象沒法釋放,從而引發內存泄漏。這種狀況下通常都會在try裏面去的鏈接,在finally裏面釋放鏈接。

        五、內部類和外部模塊等的引用 
        內部類的引用是比較容易遺忘的一種,並且一旦沒釋放可能致使一系列的後繼類對象沒有釋放。此外程序員還要當心外部模塊不經意的引用,例如程序員A 負責A 模塊,調用了B 模塊的一個方法如: 
        public void registerMsg(Object b); 
        這種調用就要很是當心了,傳入了一個對象,極可能模塊B就保持了對該對象的引用,這時候就須要注意模塊B 是否提供相應的操做去除引用。 
        六、單例模式 
        單例對象在被初始化後將在JVM的整個生命週期中存在(以靜態變量的方式),若是單例對象持有外部對象的引用,那麼這個外部對象將不能被jvm正常回收,致使內存泄露,考慮下面的例子:

        class A{
        public A(){ B.getInstance().setA(this); } .... } //B類採用單例模式 class B{ private A a; private static B instance=new B(); public B(){} public static B getInstance(){ return instance; } public void setA(A a){ this.a=a; } //getter... }
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19

        顯然B採用singleton模式,它持有一個A對象的引用,而這個A類的對象將不能被回收。想象下若是A是個比較複雜的對象或者集合類型會發生什麼狀況


        垃圾回收機制

        垃圾回收的基本算法

        標記壓縮法

        先從根節點開始對全部可達對象作一次標記,但以後,它並不簡單地清除未標記的對象,而是將全部的存活對象壓縮到內存的一端以後,清理邊界外全部的空間。這種方法既避免了碎片的產生,又不須要兩塊相同的內存空間,所以,其性價比比較高。

        標記回收法

        從「GC Roots」(GC Roots指垃圾收集器的對象,GC會收集那些不是GC roots且沒有被GC roots引用的對象)集合開始,將內存整個遍歷一次,保留全部被GC Roots直接或者間接引用到的對象,而剩下的對象都看成垃圾對待並回收,這個算法須要中斷進程內其餘組件的執行而且可能產生碎片化

        複製回收法

        將內存分爲大小相等的兩部分(假設A、B兩部分),每次呢只使用其中的一部分(這裏咱們假設爲A區),等這部分用完了,這時候就將這裏面還能活下來的對象複製到另外一部份內存(這裏設爲B區)中,而後把A區中的剩下部分所有清理掉。這樣內存碎片的問題就解決了 
        這裏寫圖片描述

        分代回收法

        根據對象的生命週期將內存劃分,而後進行分區管理,在Java虛擬機分代垃圾回收機制中,應用程序可用的堆空間能夠分爲年輕代老年代,年輕代有被分爲Eden區,From區與To區 
        這裏寫圖片描述 
        分代回收法更詳細連接http://blog.csdn.net/sinat_36246371/article/details/52998505 

        >堆內存的分代回收

        >與垃圾回收的附加選項

        下面兩個選項用於設置java虛擬機內存大小 
        -Xms :設置java虛擬機堆內存的最大容量如java -Xmx256m XxxClass 
        -Xms :設置java虛擬機堆內存的初始容量,如java -Xms128m XxxClass

        下面選項都是關於java垃圾回收的附加選項 
        -xx:MinHeapFreeRatio =40 :設置java堆內存最小的空閒百分比,默認爲40,如java -xx:MinHeapFreeRadio = 40 XxxClass

        -xx:MaxHeapFreeRatio=70 :設置Java堆內存最大的空閒百分比,默認爲70,如java -XX:MaxHeapFreeRatio =70 XxxClass

        -xx:NewRatio=2 ;設置Yonng/Old內存的比例,如java -XX:NewRatio=1 XxxClass 
        -xx:NewSize=size:設置Yonng代內存的默認容量,如java -XX:Newsize=64m XxxClass

        -xx:SurvivorRatio = 8;設置Yonng代中eden/survivor的比例,如java -xx:MaxNewSize=128m XxxClass

        注意 當設置Young代的內存超過了-Xmx設置的大小時,Young設置的內存大小將不會起做用,JVM會自動將Young代內存設置爲與-Xmx設置的大小相等。

        -XX:PermSIze=size;設置Permnanent代內存的默認容量,如java –XX:PermSize=128m XxxClass

        -XX:MaxPermSize=64m;設置Permanent代內存的最大容量,如java -XX:MaxPermSize=128m XxxClass

        >常見垃圾回收器

        1. 串行回收器(Serial Garbage Collector) 
          Serial Garbage Collector經過暫停全部應用的線程來工做。它是爲單線程工做環境而設計的。它中使用一個線程來進行垃圾回收。這種暫停應用線程來進行垃圾回收的方式可能不太適應服務器環境。它最適合簡單的命令行程序。 
          經過 -XX:+UseSerialGC 參數來選用Serial Garbage Collector。
        2. Parallel Garbage Collector

        Parallel Garbage Collector也被稱爲吞吐量收集器(throughput collector)。它是Java虛擬機的默認垃圾收集器。與Serial Garbage Collector不一樣,Parallel Garbage Collector使用多個線程進行垃圾回收。與Serial Garbage Collector類似的地方時,它也是暫停全部的應用線程來進行垃圾回收。 
        3. CMS Garbage Collector

        Concurrent Mark Sweep (CMS) Garbage Collector使用多個線程來掃描堆內存來標記須要回收的實例,而後再清除被標記的實例。CMS Garbage Collector只有在以下兩種情景纔會暫停全部的應用線程:

        當標記永久代內存空間中的對象時;
        當進行垃圾回收時,堆內存同步發生了一些變化。

        相比Parallel Garbage Collector,CMS Garbage Collector使用更多的CPU資源來確保應用有一個更好的吞吐量。若是分配更多的CPU資源能夠得到更好的性能,那麼CMS Garbage Collector是一個更好的選擇,相比Parallel Garbage Collector。

        經過 XX:+USeParNewGC 參數來選用CMS Garbage Collector。


        內存管理的小技巧

        >儘可能使用直接量

        當須要使用字符串,還有Byte,Short、integer、Long、Float、Double、Boolean、Character包裝類的實例時,不該該採用new的方式來建立對象,而應該使用直接量來建立它們 
        應該是String str="hello";而不是String str=new String("hello"); 
        後者除了在建立一個緩存在字符串緩衝池的「hello」字符串,str所引用的String對象底層還包含一個存放了h、e、l、l、o的char[ ]數組

        >使用StringBuilder和StringBuffer進行字符串鏈接

        String表明字符序列不可變的字符串,StringBuilderheStringBuffer都表明字符序列可變的字符串 
        建議

        StringBuilder st = new StringBuilder(); c = st.append(a).append(b);
        • 1
        • 2

        不建議

        String c = a+b;
        • 1

        由於這樣會在運行的時候生成大量臨時字符串,這些字符串會保存在內存中從而致使程序性能降低

        若是使用少許的字符串操做,使用 (+運算符)鏈接字符串; 
        若是頻繁的對大量字符串進行操做,則使用 
        1:全局變量或者須要多線程支持則使用StringBuffer; 
        2:局部變量或者單線程不涉及線程安全則使有StringBuilder

        >儘早釋放無用對象的引用

        >儘可能少用靜態變量

        >避免在常常調用的方法、循環中建立Java對象

        >緩存常用的對象

        >儘可能不要使用finalize方法

        >考慮使用SoftReference

        參考:《瘋狂java 突破程序員基本功的16課》

      • 轉自http://blog.csdn.net/jyxmust/article/details/54609602
相關文章
相關標籤/搜索