Java垃圾回收機制

来源:http://www.cnblogs.com/maociyuan/archive/2016/02/18/5198676.html
-Advertisement-
Play Games

Java垃圾回收機制 說到垃圾回收(Garbage Collection,GC),很多人就會自然而然地把它和Java聯繫起來。在Java中,程式員不需要去關心記憶體動態分配和垃圾回收的問題,這一切都交給了JVM來處理。顧名思義,垃圾回收就是釋放垃圾占用的空間,那麼在Java中,什麼樣的對象會被認定為“


Java垃圾回收機制

  說到垃圾回收(Garbage Collection,GC),很多人就會自然而然地把它和Java聯繫起來。在Java中,程式員不需要去關心記憶體動態分配和垃圾回收的問題,這一切都交給了JVM來處理。顧名思義,垃圾回收就是釋放垃圾占用的空間,那麼在Java中,什麼樣的對象會被認定為“垃圾”?那麼當一些對象被確定為垃圾之後,採用什麼樣的策略來進行回收(釋放空間)?在目前的商業虛擬機中,有哪些典型的垃圾收集器?下麵我們就來逐一探討這些問題。以下是本文的目錄大綱:

  一.如何確定某個對象是“垃圾”?

  二.典型的垃圾收集演算法

  三.典型的垃圾收集器

  如果有不正之處,希望諒解和批評指正,不勝感激。

一.如何確定某個對象是“垃圾”?

  在這一小節我們先瞭解一個最基本的問題:如果確定某個對象是“垃圾”?既然垃圾收集器的任務是回收垃圾對象所占的空間供新的對象使用,那麼垃圾收集器如何確定某個對象是“垃圾”?—即通過什麼方法判斷一個對象可以被回收了。

  在java中是通過引用來和對象進行關聯的,也就是說如果要操作對象,必須通過引用來進行。那麼很顯然一個簡單的辦法就是通過引用計數來判斷一個對象是否可以被回收。不失一般性,如果一個對象沒有任何引用與之關聯,則說明該對象基本不太可能在其他地方被使用到,那麼這個對象就成為可被回收的對象了。這種方式成為引用計數法。

  這種方式的特點是實現簡單,而且效率較高,但是它無法解決迴圈引用的問題,因此在Java中並沒有採用這種方式(Python採用的是引用計數法)。看下麵這段代碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Main {     public static void main(String[] args) {         MyObject object1 = new MyObject();         MyObject object2 = new MyObject();                   object1.object = object2;         object2.object = object1;                   object1 = null;         object2 = null;     } }   class MyObject{     public Object object = null; }

  最後面兩句將object1和object2賦值為null,也就是說object1和object2指向的對象已經不可能再被訪問,但是由於它們互相引用對方,導致它們的引用計數都不為0,那麼垃圾收集器就永遠不會回收它們。

  為瞭解決這個問題,在Java中採取了 可達性分析法。該方法的基本思想是通過一系列的“GC Roots”對象作為起點進行搜索,如果在“GC Roots”和一個對象之間沒有可達路徑,則稱該對象是不可達的,不過要註意的是被判定為不可達的對象不一定就會成為可回收對象。被判定為不可達的對象要成為可回收對象必須至少經歷兩次標記過程,如果在這兩次標記過程中仍然沒有逃脫成為可回收對象的可能性,則基本上就真的成為可回收對象了。

  至於可達性分析法具體是如何操作的我暫時也沒有看得很明白,如果有哪位朋友比較清楚的話請不吝指教。

  下麵來看個例子:

1 2 3 4 5 6 7 Object aobj = new Object ( ) ; Object bobj = new Object ( ) ; Object cobj = new Object ( ) ; aobj = bobj; aobj = cobj; cobj = null; aobj = null;

   第幾行有可能會使得某個對象成為可回收對象?第7行的代碼會導致有對象會成為可回收對象。至於為什麼留給讀者自己思考。

  再看一個例子:

1 2 3 String str = new String("hello"); SoftReference<String> sr = new SoftReference<String>(new String("java")); WeakReference<String> wr = new WeakReference<String>(new String("world"));

  這三句哪句會使得String對象成為可回收對象?第2句和第3句,第2句在記憶體不足的情況下會將String對象判定為可回收對象,第3句無論什麼情況下String對象都會被判定為可回收對象。

  最後總結一下平常遇到的比較常見的將對象判定為可回收對象的情況:

  1)顯示地將某個引用賦值為null或者將已經指向某個對象的引用指向新的對象,比如下麵的代碼:

1 2 3 4 5 Object obj = new Object(); obj = null; Object obj1 = new Object(); Object obj2 = new Object(); obj1 = obj2;

    2)局部引用所指向的對象,比如下麵這段代碼:

1 2 3 4 5 6 7 8 void fun() {   .....     for(int i=0;i<10;i++) {         Object obj = new Object();         System.out.println(obj.getClass());     }    }

   迴圈每執行完一次,生成的Object對象都會成為可回收的對象。

  3)只有弱引用與其關聯的對象,比如:

1 WeakReference<String> wr = new WeakReference<String>(new String("world"));

二.典型的垃圾收集演算法

  在確定了哪些垃圾可以被回收後,垃圾收集器要做的事情就是開始進行垃圾回收,但是這裡面涉及到一個問題是:如何高效地進行垃圾回收。由於Java虛擬機規範並沒有對如何實現垃圾收集器做出明確的規定,因此各個廠商的虛擬機可以採用不同的方式來實現垃圾收集器,所以在此只討論幾種常見的垃圾收集演算法的核心思想。

  1.Mark-Sweep(標記-清除)演算法

  這是最基礎的垃圾回收演算法,之所以說它是最基礎的是因為它最容易實現,思想也是最簡單的。標記-清除演算法分為兩個階段:標記階段和清除階段。標記階段的任務是標記出所有需要被回收的對象,清除階段就是回收被標記的對象所占用的空間。具體過程如下圖所示:

  從圖中可以很容易看出標記-清除演算法實現起來比較容易,但是有一個比較嚴重的問題就是容易產生記憶體碎片,碎片太多可能會導致後續過程中需要為大對象分配空間時無法找到足夠的空間而提前觸發新的一次垃圾收集動作。

  2.Copying(複製)演算法

  為瞭解決Mark-Sweep演算法的缺陷,Copying演算法就被提了出來。它將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用完了,就將還存活著的對象複製到另外一塊上面,然後再把已使用的記憶體空間一次清理掉,這樣一來就不容易出現記憶體碎片的問題。具體過程如下圖所示:

  這種演算法雖然實現簡單,運行高效且不容易產生記憶體碎片,但是卻對記憶體空間的使用做出了高昂的代價,因為能夠使用的記憶體縮減到原來的一半。

  很顯然,Copying演算法的效率跟存活對象的數目多少有很大的關係,如果存活對象很多,那麼Copying演算法的效率將會大大降低。

  3.Mark-Compact(標記-整理)演算法

  為瞭解決Copying演算法的缺陷,充分利用記憶體空間,提出了Mark-Compact演算法。該演算法標記階段和Mark-Sweep一樣,但是在完成標記之後,它不是直接清理可回收對象,而是將存活對象都向一端移動,然後清理掉端邊界以外的記憶體。具體過程如下圖所示:

  

  4.Generational Collection(分代收集)演算法

  分代收集演算法是目前大部分JVM的垃圾收集器採用的演算法。它的核心思想是根據對象存活的生命周期將記憶體劃分為若幹個不同的區域。一般情況下將堆區劃分為老年代(Tenured Generation)和新生代(Young Generation),老年代的特點是每次垃圾收集時只有少量對象需要被回收,而新生代的特點是每次垃圾回收時都有大量的對象需要被回收,那麼就可以根據不同代的特點採取最適合的收集演算法。

  目前大部分垃圾收集器對於新生代都採取Copying演算法,因為新生代中每次垃圾回收都要回收大部分對象,也就是說需要複製的操作次數較少,但是實際中並不是按照1:1的比例來劃分新生代的空間的,一般來說是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當進行回收時,將Eden和Survivor中還存活的對象複製到另一塊Survivor空間中,然後清理掉Eden和剛纔使用過的Survivor空間。

  而由於老年代的特點是每次回收都只回收少量對象,一般使用的是Mark-Compact演算法。

  註意,在堆區之外還有一個代就是永久代(Permanet Generation),它用來存儲class類、常量、方法描述等。對永久代的回收主要回收兩部分內容:廢棄常量和無用的類。

三.典型的垃圾收集器

  垃圾收集演算法是 記憶體回收的理論基礎,而垃圾收集器就是記憶體回收的具體實現。下麵介紹一下HotSpot(JDK 7)虛擬機提供的幾種垃圾收集器,用戶可以根據自己的需求組合出各個年代使用的收集器。

  1.Serial/Serial Old

  Serial/Serial Old收集器是最基本最古老的收集器,它是一個單線程收集器,並且在它進行垃圾收集時,必須暫停所有用戶線程。Serial收集器是針對新生代的收集器,採用的是Copying演算法,Serial Old收集器是針對老年代的收集器,採用的是Mark-Compact演算法。它的優點是實現簡單高效,但是缺點是會給用戶帶來停頓。

  2.ParNew

  ParNew收集器是Serial收集器的多線程版本,使用多個線程進行垃圾收集。

  3.Parallel Scavenge

  Parallel Scavenge收集器是一個新生代的多線程收集器(並行收集器),它在回收期間不需要暫停其他用戶線程,其採用的是Copying演算法,該收集器與前兩個收集器有所不同,它主要是為了達到一個可控的吞吐量。

  4.Parallel Old

  Parallel Old是Parallel Scavenge收集器的老年代版本(並行收集器),使用多線程和Mark-Compact演算法。

  5.CMS

  CMS(Current Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器,它是一種併發收集器,採用的是Mark-Sweep演算法。

  6.G1

  G1收集器是當今收集器技術發展最前沿的成果,它是一款面向服務端應用的收集器,它能充分利用多CPU、多核環境。因此它是一款並行與併發收集器,並且它能建立可預測的停頓時間模型。

  下麵補充一下關於記憶體分配方面的東西:

  

  對象的記憶體分配,往大方向上講就是在堆上分配,對象主要分配在新生代的Eden Space和From Space,少數情況下會直接分配在老年代。如果新生代的Eden Space和From Space的空間不足,則會發起一次GC,如果進行了GC之後,Eden Space和From Space能夠容納該對象就放在Eden Space和From Space。在GC的過程中,會將Eden Space和From  Space中的存活對象移動到To Space,然後將Eden Space和From Space進行清理。如果在清理的過程中,To Space無法足夠來存儲某個對象,就會將該對象移動到老年代中。在進行了GC之後,使用的便是Eden space和To Space了,下次GC時會將存活對象複製到From Space,如此反覆迴圈。當對象在Survivor區躲過一次GC的話,其對象年齡便會加1,預設情況下,如果對象年齡達到15歲,就會移動到老年代中。

  一般來說,大對象會被直接分配到老年代,所謂的大對象是指需要大量連續存儲空間的對象,最常見的一種大對象就是大數組,比如:

  byte[] data = new byte[4*1024*1024]

  這種一般會直接在老年代分配存儲空間。

  當然分配的規則並不是百分之百固定的,這要取決於當前使用的是哪種垃圾收集器組合和JVM的相關參數。

  參考資料:

  《深入理解Java虛擬機》

  原文地址:http://www.cnblogs.com/dolphin0520/p/3783345.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近在做的一個項目其中的一部分是與遠程伺服器進行交互,確定身份驗證的合法性,於是編寫了SendRequest方法 此方法發送給遠程伺服器XML請求,伺服器經過處理後,返回XML回應,由此方法接收到後進行返回。 1 protected string SendRequest(string strXML)
  • 一、創建拖動組件 0.Draggable組件不依賴於其他組件 1.使用標簽創建 拖動組件 2.使用js創建 拖動組件 二、屬性 1.revert:如果設置為true,在拖動停止時元素將返回起始位置 2.cursor:拖動時的CSS指針樣式 $(function () { $("#box").drag...
  • 在這篇文章中,將介紹一些提高 ASP.NET Web 應用性能的方法和技巧。眾所周知,解決性能問題是一項繁瑣的工作,當出現性能問題,每個人都會歸咎於編寫代碼的開發人員。 以下為譯文 那性能問題到底該如何解決?以下是應用系統發佈前,作為 .NET 開發人員需要檢查的點。 1.debug=「false」
  • Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpo
  • 在eclipse里jsp編譯後的java和class文件的位置 eclipse版本不一樣,位置也不一樣第一種:1.java類編譯後產生的.class文件在D:\workspace\test\WEB-INF\classes下; 2.jsp產生的JAVA類文件則在D:\workspace\test\wo
  • 話說用了就要有點產出,要不然過段時間又忘了,所以在這裡就記錄一下試用Kafka的安裝過程和php擴展的試用。 實話說,如果用於隊列的話,跟PHP比較配的,還是Redis。用的順手,呵呵,只是Redis不能有多個consumer。但Kafka官方對PHP不支持,PHP擴展是愛好者或使用者寫的。下麵就開
  • 一、簡介 http://www.xuebuyuan.com/2195578.html 二、教程 http://dev.ariel-networks.com/apr/
  • 有時候,由於初期考慮不周,或者後期的需求變化,一些普通變數可能也會有線程安全的需求。
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...