1. 程式人生 > >常見垃圾回收方法

常見垃圾回收方法

多線程 產生 避免 能開 兩個 過渡 移除 代理 內存管理

1、標記清除法(Mark And Sweep)

第一步:從根部出發,遍歷全局,然後對所有可達的對象進行標記

第二步:對所有未標記的對象進行清除

優點:方法簡單,速度較快。缺點:容易產生較多的內存碎片。

采用這種方式的語言:lua等

2、標記整理回收(mark-compact)

第一步和標記清除法一樣,標記所有可達對象

第二步將未標記的對象清除,同時將現有對象的空間合並

優點:沒有內存碎片。缺點:合並空間的時候,引用該對象的所有線程都會被掛起,合並完成後才會重新執行。

采用這種方式的語言:c#等

3、標記復制回收(mark-copy)

復制算法開辟了兩個相等的空間,每次只使用其中的一塊空間

第一步標記

第二步將所有標記過的對象,復制到另一塊空間,當復制完成後,指向原有對象的指針指向新的對象。全部復制完成後,釋放原有的空間。

優點:沒有內存碎片,不會gc ,效率高 缺點:需要額外的內存空間

采用這種方式的語言:java的新生代

4、引用計數算法(reference counting)

對象每次被引用的時候對引用次數加1,每次被引用對象被刪除時,則對引用次數減1,當引用計數為0時,則刪除對象。

優點:迅速,每次當對象引用次數為0時,則馬上就會被清除。無需系統支持,去確定程序的根。

缺點:

  1、計數賦值器帶來額外的開銷。所以不適合通用的大容量的內存管理器。

  2、多線程的程序中,可能釋放過早。引用計數的存儲指針操作是原子化的,並發線程卻同時進行讀取和修改,開發者要避免更新指針槽過程中出現的競爭問題。

  3、對單個對象的簡單操作也會引發內存請求(更新引用次數),會“汙染”高速緩存

  4、無法解決循環引用問題

  5、有可能卡頓,當刪除一個大的根節點的時候,需要去遞歸刪除每一個子孫節點。

循環引用的解決方法:

  1、定期用標記算法作為補充處理

  2、設為強引用和弱引用,把可能產生環的引用設為弱引用,所有強引用可達且不成環,當強引用次數為0時,刪除對象(這種方法為了一些安全性原因,性能開銷大,只有少數語言使用)

    c++的智能指針的弱引用和這種算法的弱引用不一樣,c++的弱引用只能確定是否可達,主要是為了避免非法訪問。

  3、部分跟蹤算法,循環引用指針出現有2個條件:

    (1)環狀指針內部,所有引用對象都有內部對象指針產生

    (2)如果刪除某一對象後,引用計數仍然為0,則說明產生了環狀

    掃描對象,如果一個對象的所有引用都是循環引用,則進行處理。臨時移除對目標對象的引用次數,從而移除內部指針的引用次數,如果目標對象引用計數仍然大於0,則說明存在外部引用,否則一起處理掉。

計數回收的語言有:python等

5、分代算法

將內存分為幾個區域,不同狀態的對象放進不同的區域裏,對每個區域采取不同的垃圾回收策略,可以兼顧優點,但是比較復雜。

采用分代回收的語言:java等

java將內存分成了新生代、年老代和永久代

新生代:新生代用標記復制回收,因為絕大部分創建的對象都是臨時用的,很快會被回收掉,同時為了提高性能,和適合用復制回收,復制回收的兩塊區域大小是9:1。

年老代:當在新生代裏復制一定次數還沒有被回收以後,則放到年老代裏,年老代采用復制標記回收。

永生代:當在年老代理一定時間沒有被回收,則放入永生代,永生代采用復制整理回收。

分成的好處針對不同性質的對象,采用不同的處理方式。復制整理回收的回收效果好,但是整理過程中會造成gc,所以用了兩層過渡,減少復制整理的發生。

常見垃圾回收方法