常見垃圾回收方法
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,所以用了兩層過渡,減少復制整理的發生。
常見垃圾回收方法