目錄
什麼是Java的垃圾回收機制
垃圾回收機制,簡稱 GC
- Java 語言不需要程序員直接控制內存回收,由 JVM 在後臺自動回收不再使用的內存
- 提高編程效率
- 保護程序的完整性
- JVM 需要跟蹤程序中有用的對象,確定哪些是無用的,影響性能
Java垃圾回收與c語言的垃圾回收最大的區別在於:java的垃圾回收由jvm在後臺自動回收不在使用的內存,而C語言需要人爲的控制內存的回收。
垃圾回收機制的意義
內存也只是一個容器,如果說一直往內存放數據,而不進行管理與清除回收的話,總有一天,這塊區域會被完全佔滿,而且在這個過程中還可能會出現兩個問題:內存泄漏與內存溢出。
內存泄漏與內存溢出
概念
內存泄漏:應用程序不再使用對象,但是垃圾回收器無法刪除它們,因爲它們已被引用。
內存溢出:指程序申請內存時,沒有足夠的內存供申請者使用,或者說,給了你一塊存儲int類型數據的存儲空間,但是你卻存儲long類型的數據,那麼結果就是內存不夠用,此時就會報錯OOM,即所謂的內存溢出。
大量的內存泄漏的後果就是會造成內存溢出,但是內存溢出不一定是因內存泄漏造成的。
內存溢出的原因
1.內存中加載的數據量過於龐大,如一次從數據庫取出過多數據;
2.集合類中有對對象的引用,使用完後未清空,使得JVM不能回收;
3.代碼中存在死循環或循環產生過多重複的對象實體;
4.使用的第三方軟件中的BUG;
5.啓動參數內存值設定的過小
解決內存溢出的方法
- 修改JVM啓動參數
(-Xms:設置初始分配大小, -Xmx:最大分配內存)
- 檢查錯誤日誌
- 對代碼進行分析,查找可能會造成內存溢出的地方
垃圾回收
如何確定哪些對象需要進行回收
此處有兩種方法來判斷:引用計數法和可達性分析算法
引用計數發:給對象添加一個引用計數器,如果有對該對象引用時,那麼計數器加1,引用失效時,計數器減1.但是現在jvm並沒有使用這個算法,因爲不能解決對象間循環引用的問題
可達性分析算法:通過判斷對象的引用鏈是否可達來決定對象是否可以被回收。程序把所有的引用關係看成一張圖,由GC Roots的對象作爲起始點,從這些節點開始向下搜索,搜索的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何一個引用鏈時,則認爲此對象不可用。
此圖中雖然Object5還保留有對6和7的引用,但是已經沒有引用鏈通向GC Roots,所以認爲該對象已經沒有引用。
在java中可以作爲GC Roots的對象:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象;
- 方法區中類靜態屬性引用的對象;
- 方法區中常量引用的對象;
- 本地方法棧中 JNI(Native 方法)引用的對象。
什麼時候進行回收操作
- CPU空閒的時候自動進行回收
- 堆內存滿了之後
- 主動調用System.gc()
如何回收
標記-清除算法
最基礎的一種算法,分爲兩步。第一步進行標記,第二步進行回收。經過分析後對可以回收的對象進行標記,而後回收被標記的對象。
優點:簡單易懂
缺點:回收之後會產生不連續的內存碎片,影響存儲。
執行如下:
回收之前
執行之後:
標記-整理算法
與標記-清除算法類似,,區別在於對可回收對象回收後還會對存貨對象進行整理,不會產生不連續的內存碎片。
執行如下:
回收之前:
回收之後:
複製算法
將內存分爲大小相等的兩塊,每次只使用其中的一塊,垃圾收集時,遍歷該內存空間內所有的對象,將還存活的對象複製到另一塊內存中,然後清除這一塊的所有的對象。這樣就不用考慮會產生不連續的內存碎片,只是原本的空間會被分爲兩份,導致可用空間變小,回收頻率會增加
執行如下:
回收之前:
回收之後:
分代算法
分代算法其實並不是一個新的算法,只是不同的對象生命週期不同,故採取不同的算法回收已提高效率。
根據生命週期的不同,將內存劃分爲:年輕代,老年代,永久代。
新生代
大部分新產生的對象都是存放在新生代中,目的就是快速的回收那些生命週期較短的對象,這種回收也稱爲Minor GC,使用的算法爲複製算法。
新生代又劃分爲3部分,一個Eden區和兩個Survivor區(Survivor1和Survivor2),默認情況下內存大小比例爲8:1:1。新產生的大部分對象都是存在於Eden區,當發生回收時,會將存活對象複製到Survivor1中,然後清空Eden區。當Survivor1也存滿時,會將Survivor1與Eden區的存活對象複製到Survivor2中,清空Eden與Survivor1。
出現以下兩種情況時,則不再將存活對象複製到Survivor2而是老年代:
1)、當Survivor2內存不夠存放Eden與Survivor1的存活對象時,會直接複製到老年代
2)、在Survivor中經過多次GC後依然存活的對象,默認是15次。
由於新生代的對象生命週期較短,產生對象的速度也快,所以Minor GC頻率高,但是這種方法不會影響到老年代的GC。
老年代
前面說到大部分的新生的對象都是存入到新生代,當然還有例外,那就是較大的新生對象會直接放入老年代而不是新生代,因爲新生代與老年代在配置時,新生代的內存會遠小於老年代,如果對象較大的話可能放不下。
老年代中存儲的對象還有就是經過新生代多次GC後依舊存活的對象,所以老年代中存儲的對象生命週期都比較長,故使用的算法爲標記清除和標記整理算法,這種回收也稱爲Full GC。
Full GC的頻率較少,但是Full GC是針對整個堆內存的回收,耗時長。
永久代
JDK8取消了永久代,誕生了元空間。元空間與永久代的本質類似,但是有個最大的區別就是:元空間使用的內存區域不在虛擬機,而是本地空間。
垃圾收集器
新生代使用的:Serial、ParNew、Parallel Scavenge
老年代使用的:Serial Old、Parallel Old、CMS
還有一個堆回收器:G1