1. 程式人生 > >Java虛擬機器詳解04----GC演算法和種類【重要】

Java虛擬機器詳解04----GC演算法和種類【重要】

【宣告】 

歡迎轉載,但請保留文章原始出處→_→ 

本文主要內容:

  • GC的概念
  • GC演算法

    引用計數法(無法解決迴圈引用的問題,不被java採納)

      根搜尋演算法

      現代虛擬機器中的垃圾蒐集演算法:

      標記-清除

      複製演算法(新生代)

      標記-壓縮(老年代)

      分代收集

  • Stop-The-World

一、GC的概念:

  • GC:Garbage Collection 垃圾收集
  • 1960年 Lisp使用了GC
  • Java中,GC的物件是Java堆和方法區(即永久區)

我們接下來對上面的三句話進行一一的解釋:

(1)GC:Garbage Collection 垃圾收集。這裡所謂的垃圾指的是在系統執行過程當中所產生的一些無用的物件,這些物件佔據著一定的記憶體空間,如果長期不被釋放,可能導致OOM

在C/C++裡是由程式猿自己去申請、管理和釋放記憶體空間,因此沒有GC的概念。而在Java中,後臺專門有一個專門用於垃圾回收的執行緒來進行監控、掃描,自動將一些無用的記憶體進行釋放,這就是垃圾收集的一個基本思想,目的在於防止由程式猿引入的人為的記憶體洩露

(2)事實上,GC的歷史比Java久遠,1960年誕生於MIT的Lisp是第一門真正使用記憶體動態分配和垃圾收集技術的語言。當Lisp還在胚胎時期時,人們就在思考GC需要完成的3件事情:

哪些記憶體需要回收?

什麼時候回收?

如何回收?

(3)記憶體區域中的程式計數器、虛擬機器棧、本地方法棧這3個區域隨著執行緒而生,執行緒而滅;棧中的棧幀隨著方法的進入和退出而有條不紊地執行著出棧和入棧的操作,每個棧幀中分配多少記憶體基本是在類結構確定下來時就已知的。在這幾個區域不需要過多考慮回收的問題,因為方法結束或者執行緒結束時,記憶體自然就跟著回收了

Java堆和方法區則不同,一個介面中的多個實現類需要的記憶體可能不同,一個方法中的多個分支需要的記憶體也可能不一樣,我們只有在程式處於執行期間時才能知道會建立哪些物件,這部分記憶體的分配和回收都是動態的,GC關注的也是這部分記憶體,後面的文章中如果涉及到“記憶體”分配與回收也僅指著一部分記憶體。

二、引用計數演算法:(老牌垃圾回收演算法。無法處理迴圈引用,沒有被Java採納)

1、引用計數演算法的概念:

給物件中新增一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的物件就是不可能再被使用的。

e6217360-0985-46e8-88fd-022f1fc0fba5

2、使用者舉例:

引用計數演算法的實現簡單,判定效率也高,大部分情況下是一個不錯的演算法。很多地方應用到它。例如:

微軟公司的COM技術:Computer Object Model

使用ActionScript3的FlashPlayer

Python

但是,主流的java虛擬機器並沒有選用引用計數演算法來管理記憶體,其中最主要的原因是:它很難解決物件之間相互迴圈引用的問題

3、引用計數演算法的問題:

  • 引用和去引用伴隨加法和減法,影響效能
  • 致命的缺陷:對於迴圈引用的物件無法進行回收

1a489e67-e047-408f-a97e-4a141e6ab3b0

上面的3個圖中,對於最右邊的那張圖而言:迴圈引用的計數器都不為0,但是他們對於根物件都已經不可達了,但是無法釋放。

迴圈引用的程式碼舉例:

 1 public class Object {
 2 
 3     Object field = null;
 4     
 5     public static void main(String[] args) {
 6         Thread thread = new Thread(new Runnable() {
 7             public void run() {
 8                 Object objectA = new Object();
 9                 Object objectB = new Object();//位置1
10                 objectA.field = objectB;
11                 objectB.field = objectA;//位置2
12                 //to do something
13                 objectA = null;
14                 objectB = null;//位置3
15             }
16         });
17         thread.start();
18         while (true);
19     }
20     
21 }

上方程式碼看起來有點刻意為之,但其實在實際程式設計過程當中,是經常出現的,比如兩個一對一關係的資料庫物件,各自保持著對方的引用。最後一個無限迴圈只是為了保持JVM不退出,沒什麼實際意義。

程式碼解釋:

程式碼中標註了1、2、3三個數字,當位置1的語句執行完以後,兩個物件的引用計數全部為1。當位置2的語句執行完以後,兩個物件的引用計數就全部變成了2。當位置3的語句執行完以後,也就是將二者全部歸為空值以後,二者的引用計數仍然為1。根據引用計數演算法的回收規則,引用計數沒有歸0的時候是不會被回收的。

對於我們現在使用的GC來說,當thread執行緒執行結束後,會將objectA和objectB全部作為待回收的物件。而果我們的GC採用上面所說的引用計數演算法,則這兩個物件永遠不會被回收,即便我們在使用後顯示的將物件歸為空值也毫無作用。

三、根搜尋演算法:

1、根搜尋演算法的概念:

  由於引用計數演算法的缺陷,所以JVM一般會採用一種新的演算法,叫做根搜尋演算法。它的處理方式就是,立若干種根物件,當任何一個根物件到某一個物件均不可達時,則認為這個物件是可以被回收的

7ab0f17b-13f7-4886-a24d-3813c2173891

如上圖所示,ObjectD和ObjectE是互相關聯的,但是由於GC roots到這兩個物件不可達,所以最終D和E還是會被當做GC的物件,上圖若是採用引用計數法,則A-E五個物件都不會被回收。

2、可達性分析:

 我們剛剛提到,設立若干種根物件,當任何一個根物件到某一個物件均不可達時,則認為這個物件是可以被回收的。我們在後面介紹標記-清理演算法/標記整理演算法時,也會一直強調從根節點開始,對所有可達物件做一次標記,那什麼叫做可達呢?這裡解釋如下:

可達性分析:

  從根(GC Roots)的物件作為起始點,開始向下搜尋,搜尋所走過的路徑稱為引用鏈”,當一個物件到GC Roots沒有任何引用鏈相連(用圖論的概念來講,就是從GC Roots到這個物件不可達)時,則證明此物件是不可用的。

3、根(GC Roots):

說到GC roots(GC根),在JAVA語言中,可以當做GC roots的物件有以下幾種:

1、棧(棧幀中的本地變量表)中引用的物件。

2、方法區中的靜態成員。

3、方法區中的常量引用的物件(全域性變數)

4、本地方法棧中JNI(一般說的Native方法)引用的物件。

注:第一和第四種都是指的方法的本地變量表,第二種表達的意思比較清晰,第三種主要指的是宣告為final的常量值。

在根搜尋演算法的基礎上,現代虛擬機器的實現當中,垃圾蒐集的演算法主要有三種,分別是標記-清除演算法、複製演算法、標記-整理演算法。這三種演算法都擴充了根搜尋演算法,不過它們理解起來還是非常好理解的。

四、標記-清除演算法:

1、標記清除演算法的概念:

標記-清除演算法是現代垃圾回收演算法的思想基礎。標記-清除演算法將垃圾回收分為兩個階段:標記階段和清除階段。一種可行的實現是,在標記階段,首先通過根節點,標記所有從根節點開始的可達物件。因此,未被標記的物件就是未被引用的垃圾物件;然後,在清除階段,清除所有未被標記的物件。

7de44970-2e02-46a1-a5d0-0663b21906c6

2、標記-清除演算法詳解:

它的做法是當堆中的有效記憶體空間(available memory)被耗盡的時候,就會停止整個程式(也被成為stop the world),然後進行兩項工作,第一項則是標記,第二項則是清除。

  • 標記:標記的過程其實就是,遍歷所有的GC Roots,然後將所有GC Roots可達的物件標記為存活的物件
  • 清除:清除的過程將遍歷堆中所有的物件,將沒有標記的物件全部清除掉

也就是說,就是當程式執行期間,若可以使用的記憶體被耗盡的時候,GC執行緒就會被觸發並將程式暫停,隨後將依舊存活的物件標記一遍,最終再將堆中所有沒被標記的物件全部清除掉,接下來便讓程式恢復執行

來看下面這張圖:

47146934-c3a3-4976-991f-77e84ae008cc

上圖代表的是程式執行期間所有物件的狀態,它們的標誌位全部是0(也就是未標記,以下預設0就是未標記,1為已標記),假設這會兒有效記憶體空間耗盡了,JVM將會停止應用程式的執行並開啟GC執行緒,然後開始進行標記工作,按照根搜尋演算法,標記完以後,物件的狀態如下圖:

5cbf57ce-c83a-40d2-b58a-b37d3eee3803

上圖中可以看到,按照根搜尋演算法,所有從root物件可達的物件就被標記為了存活的物件,此時已經完成了第一階段標記。接下來,就要執行第二階段清除了,那麼清除完以後,剩下的物件以及物件的狀態如下圖所示:

8654ed59-fc00-446d-8995-a02ab57cf213

上圖可以看到,沒有被標記的物件將會回收清除掉,而被標記的物件將會留下,並且會將標記位重新歸0。接下來就不用說了,喚醒停止的程式執行緒,讓程式繼續執行即可。

疑問:為什麼非要停止程式的執行呢?

答:

這個其實也不難理解,假設我們的程式與GC執行緒是一起執行的,各位試想這樣一種場景。

假設我們剛標記完圖中最右邊的那個物件,暫且記為A,結果此時在程式當中又new了一個新物件B,且A物件可以到達B物件。但是由於此時A物件已經標記結束,B物件此時的標記位依然是0,因為它錯過了標記階段。因此當接下來輪到清除階段的時候,新物件B將會被苦逼的清除掉。如此一來,不難想象結果,GC執行緒將會導致程式無法正常工作。

上面的結果當然令人無法接受,我們剛new了一個物件,結果經過一次GC,忽然變成null了,這還怎麼玩?

3、標記-清除演算法的缺點:

(1)首先,它的缺點就是效率比較低(遞迴與全堆物件遍歷),導致stop the world的時間比較長,尤其對於互動式的應用程式來說簡直是無法接受。試想一下,如果你玩一個網站,這個網站一個小時就掛五分鐘,你還玩嗎?

(2)第二點主要的缺點,則是這種方式清理出來的空閒記憶體是不連續的,這點不難理解,我們的死亡物件都是隨即的出現在記憶體的各個角落的,現在把它們清除之後,記憶體的佈局自然會亂七八糟。而為了應付這一點,JVM就不得不維持一個記憶體的空閒列表,這又是一種開銷。而且在分配陣列物件的時候,尋找連續的記憶體空間會不太好找。

五、複製演算法:(新生代的GC)

複製演算法的概念:

將原有的記憶體空間分為兩塊,每次只使用其中一塊,在垃圾回收時,將正在使用的記憶體中的存活物件複製到未使用的記憶體塊中,之後,清除正在使用的記憶體塊中的所有物件,交換兩個記憶體的角色,完成垃圾回收

  • 與標記-清除演算法相比,複製演算法是一種相對高效的回收方法
  • 不適用於存活物件較多的場合,如老年代(複製演算法適合做新生代的GC

ff1e1846-e49c-4663-aee1-7c63628f567c

  • 複製演算法的最大的問題是:空間的浪費

複製演算法使得每次都只對整個半區進行記憶體回收,記憶體分配時也就不用考慮記憶體碎片等複雜情況,只要移動堆頂指標,按順序分配記憶體即可,實現簡單,執行高效。只是這種演算法的代價是將記憶體縮小為原來的一半,這個太要命了。

所以從以上描述不難看出,複製演算法要想使用,最起碼物件的存活率要非常低才行,而且最重要的是,我們必須要克服50%記憶體的浪費。

現在的商業虛擬機器都採用這種收集演算法來回收新生代,新生代中的物件98%都是“朝生夕死”的,所以並不需要按照1:1的比例來劃分記憶體空間,而是將記憶體分為一塊比較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,將Eden和Survivor中還存活著的物件一次性地複製到另外一塊Survivor空間上,最後清理掉Eden和剛才用過的Survivor空間。HotSpot虛擬機器預設Eden和Survivor的大小比例是8:1,也就是說,每次新生代中可用記憶體空間為整個新生代容量的90%(80%+10%),只有10%的空間會被浪費。

當然,98%的物件可回收只是一般場景下的資料,我們沒有辦法保證每次回收都只有不多於10%的物件存活,當Survivor空間不夠用時,需要依賴於老年代進行分配擔保,所以大物件直接進入老年代。整個過程如下圖所示:

7e1f6ed2-e0c4-45e4-b7db-b59c28e1ee9c

上圖中,綠色箭頭的位置代表的是大物件,大物件直接進入老年代。

根據上面的複製演算法,現在我們來看下面的這個gc日誌的數字,就應該能看得懂了吧:

6d59301f-f0c9-4fed-ba36-e66bc6574e8f

上方GC日誌中,新生代的可用空間是13824K(eden區的12288K+from space的1536K)。而根據記憶體的地址計算得知,新生代的總空間為15M,而這個15M的空間是 = 13824K +to space 的 1536K。

六、標記-整理演算法:(老年代的GC)

引入:

    如果在物件存活率較高時就要進行較多的複製操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的記憶體中所有物件都100%存活的極端情況,所以在老年代一般不能直接選中這種演算法。

概念:

標記-壓縮演算法適合用於存活物件較多的場合,如老年代。它在標記-清除演算法的基礎上做了一些優化。和標記-清除演算法一樣,標記-壓縮演算法也首先需要從根節點開始,對所有可達物件做一次標記;但之後,它並不簡單的清理未標記的物件,而是將所有的存活物件壓縮到記憶體的一端;之後,清理邊界外所有的空間

cc79889a-0856-4018-92c3-c51108c9caea

  • 標記:它的第一個階段與標記/清除演算法是一模一樣的,均是遍歷GC Roots,然後將存活的物件標記。
  • 整理:移動所有存活的物件,且按照記憶體地址次序依次排列,然後將末端記憶體地址以後的記憶體全部回收。因此,第二階段才稱為整理階段。

上圖中可以看到,標記的存活物件將會被整理,按照記憶體地址依次排列,而未被標記的記憶體會被清理掉。如此一來,當我們需要給新物件分配記憶體時,JVM只需要持有一個記憶體的起始地址即可,這比維護一個空閒列表顯然少了許多開銷。

標記/整理演算法不僅可以彌補標記/清除演算法當中,記憶體區域分散的缺點,也消除了複製演算法當中,記憶體減半的高額代價

  • 但是,標記/整理演算法唯一的缺點就是效率也不高。

不僅要標記所有存活物件,還要整理所有存活物件的引用地址。從效率上來說,標記/整理演算法要低於複製演算法。

標記-清除演算法、複製演算法、標記整理演算法的總結:

三個演算法都基於根搜尋演算法去判斷一個物件是否應該被回收,而支撐根搜尋演算法可以正常工作的理論依據,就是語法中變數作用域的相關內容。因此,要想防止記憶體洩露,最根本的辦法就是掌握好變數作用域,而不應該使用C/C++式記憶體管理方式。

在GC執行緒開啟時,或者說GC過程開始時,它們都要暫停應用程式(stop the world)。

它們的區別如下:(>表示前者要優於後者,=表示兩者效果一樣)

(1)效率複製演算法>標記/整理演算法>標記/清除演算法(此處的效率只是簡單的對比時間複雜度,實際情況不一定如此)。

(2)記憶體整齊度:複製演算法=標記/整理演算法>標記/清除演算法。

(3)記憶體利用率:標記/整理演算法=標記/清除演算法>複製演算法。

注1:可以看到標記/清除演算法是比較落後的演算法了,但是後兩種演算法卻是在此基礎上建立的。

注2:時間與空間不可兼得

七、分代收集演算法:(新生代的GC+老年代的GC)

當前商業虛擬機器的GC都是採用的“分代收集演算法”,這並不是什麼新的思想,只是根據物件的存活週期的不同將記憶體劃分為幾塊兒。一般是把Java堆分為新生代和老年代:短命物件歸為新生代,長命物件歸為老年代

  • 少量物件存活,適合複製演算法:在新生代中,每次GC時都發現有大批物件死去,只有少量存活,那就選用複製演算法,只需要付出少量存活物件的複製成本就可以完成GC。
  • 大量物件存活,適合用標記-清理/標記-整理:在老年代中,因為物件存活率高、沒有額外空間對他進行分配擔保,就必須使用“標記-清理”/“標記-整理”演算法進行GC。

注:老年代的物件中,有一小部分是因為在新生代回收時,老年代做擔保,進來的物件;絕大部分物件是因為很多次GC都沒有被回收掉而進入老年代

八、可觸及性:

所有的演算法,需要能夠識別一個垃圾物件,因此需要給出一個可觸及性的定義。

可觸及的:

  從根節點可以觸及到這個物件。

      其實就是從根節點掃描,只要這個物件在引用鏈中,那就是可觸及的。

可復活的:

  一旦所有引用被釋放,就是可復活狀態

  因為在finalize()中可能復活該物件

不可觸及的:

  在finalize()後,可能會進入不可觸及狀態

  不可觸及的物件不可能復活

      要被回收。

finalize方法復活物件的程式碼舉例:

 1 package test03;
 2 
 3 /**
 4  * Created by smyhvae on 2015/8/19.
 5  */
 6 public class CanReliveObj {
 7     public static CanReliveObj obj;
 8 
 9     //當執行GC時,會執行finalize方法,並且只會執行一次
10     @Override
11     protected void finalize() throws Throwable {
12         super.finalize();
13         System.out.println("CanReliveObj finalize called");
14         obj = this;   //當執行GC時,會執行finalize方法,然後這一行程式碼的作用是將null的object復活一下,然後變成了可觸及性
15     }
16 
17     @Override
18     public String toString() {
19         return "I am CanReliveObj";
20     }
21 
22     public static void main(String[] args) throws
23             InterruptedException {
24         obj = new CanReliveObj();
25         obj = null;   //可復活
26         System.out.println("第一次gc");
27         System.gc();
28         Thread.sleep(1000);
29         if (obj == null) {
30             System.out.println("obj 是 null");
31         } else {
32             System.out.println("obj 可用");
33         }
34         obj = null;    //不可復活
35         System.out.println("第二次gc");
36         System.gc();
37         Thread.sleep(1000);
38         if (obj == null) {
39             System.out.println("obj 是 null");
40         } else {
41             System.out.println("obj 可用");
42         }
43     }
44 }

我們需要注意第14行的註釋。一開始,我們在第25行將obj設定為null,然後執行一次GC,本以為obj會被回收掉,其實並沒有,因為GC的時候會呼叫11行的finalize方法,然後obj在第14行被複活了。緊接著又在第34行設定obj設定為null,然後執行一次GC,此時obj就被回收掉了,因為finalize方法只會執行一次。

31011217-d3a2-4e5b-9503-4f0b9bad9161

finalize方法的使用總結:

  • 經驗:避免使用finalize(),操作不慎可能導致錯誤。
  • 優先順序低,何時被呼叫,不確定

何時發生GC不確定,自然也就不知道finalize方法什麼時候執行

  • 如果要使用finalize去釋放資源,我們可以使用try-catch-finally來替代它

九、Stop-The-World:

1、Stop-The-World概念:

  Java中一種全域性暫停的現象。

全域性停頓,所有Java程式碼停止,native程式碼可以執行,但不能和JVM互動

多半情況下是由於GC引起

    少數情況下由其他情況下引起,如:Dump執行緒、死鎖檢查、堆Dump。

2、GC時為什麼會有全域性停頓?

    (1)避免無法徹底清理乾淨

打個比方:類比在聚會,突然GC要過來打掃房間,聚會時很亂,又有新的垃圾產生,房間永遠打掃不乾淨,只有讓大家停止活動了,才能將房間打掃乾淨。

    況且,如果沒有全域性停頓,會給GC執行緒造成很大的負擔,GC演算法的難度也會增加,GC很難去判斷哪些是垃圾。

  (2)GC的工作必須在一個能確保一致性的快照中進行。

這裡的一致性的意思是:在整個分析期間整個執行系統看起來就像被凍結在某個時間點上,不可以出現分析過程中物件引用關係還在不斷變化的情況,該點不滿足的話分析結果的準確性無法得到保證。

這點是導致GC進行時必須停頓所有Java執行執行緒的其中一個重要原因。

3、Stop-The-World的危害:

長時間服務停止,沒有響應(將使用者正常工作的執行緒全部暫停掉)

遇到HA系統,可能引起主備切換,嚴重危害生產環境。

  備註:HA:High Available, 高可用性叢集。

d07bb3ea-1235-41d5-9fb1-56b4087d1acf

比如上面的這主機和備機:現在是主機在工作,此時如果主機正在GC造成長時間停頓,那麼備機就會監測到主機沒有工作,於是備機開始工作了;但是主機不工作只是暫時的,當GC結束之後,主機又開始工作了,那麼這樣的話,主機和備機就同時工作了。主機和備機同時工作其實是非常危險的,很有可能會導致應用程式不一致、不能提供正常的服務等,進而影響生產環境。

程式碼舉例:

(1)列印日誌的程式碼:(每隔100ms列印一條)

public static class PrintThread extends Thread{
    public static final long starttime=System.currentTimeMillis();
    @Override
    public void run(){
        try{
            while(true){
                long t=System.currentTimeMillis()-starttime;
                System.out.println("time:"+t);
                Thread.sleep(100);
            }
        }catch(Exception e){

        }
    }
}

上方程式碼中,是負責列印日誌的程式碼,每隔100ms列印一條,並計算列印的時間。

(2)工作執行緒的程式碼:(工作執行緒,專門用來消耗記憶體)

public static class MyThread extends Thread{
    HashMap<Long,byte[]> map=new HashMap<Long,byte[]>();
    @Override
    public void run(){
        try{
            while(true){
                if(map.size()*512/1024/1024>=450){   //如果map消耗的記憶體消耗大於450時,那就清理記憶體
                    System.out.println("=====準備清理=====:"+map.size());
                    map.clear();
                }

                for(int i=0;i<1024;i++){
                    map.put(System.nanoTime(), new byte[512]);
                }
                Thread.sleep(1);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

然後,我們設定gc的引數為:

-Xmx512M -Xms512M -XX:+UseSerialGC -Xloggc:gc.log -XX:+PrintGCDetails -Xmn1m -XX:PretenureSizeThreshold=50 -XX:MaxTenuringThreshold=1

列印日誌如下:

8a8de388-7989-47f2-a7e1-496487e4be57

上圖中,紅色字型代表的正在GC。按道理來說,應該是每隔100ms會列印輸出一條日誌,但是當執行GC的時候,會出現全域性停頓的情況,導致沒有按時輸出。

下篇文章中,我們將對各種垃圾收集器進行介紹。

手有玫瑰,贈人餘香。支付寶掃一掃吧:

相關推薦

Java虛擬機器04----GC演算法種類重要

【宣告】  歡迎轉載,但請保留文章原始出處→_→  本文主要內容: GC的概念 GC演算法     引用計數法(無法解決迴圈引用的問題,不被java採納)       根搜尋演算法       現代虛擬機

Java虛擬機器----GC演算法種類重要

轉載自:http://www.cnblogs.com/smyhvae/p/4744233.html 本文主要內容: GC的概念GC演算法    引用計數法(無法解決迴圈引用的問題,不被java採納)       根搜尋演算法       現代虛擬機器中的垃圾蒐集演算法:

Java虛擬04----GC算法種類重要

置1 可用 死鎖 collect 一個 描述 player use 遍歷 【聲明】 歡迎轉載,但請保留文章原始出處→_→ 生命壹號:http://www.cnblogs.com/smyhvae/ 文章來源:http://www.cnblogs.com/smyhvae/p

JVM內幕:Java虛擬機器

這篇文章解釋了Java 虛擬機器(JVM)的內部架構。下圖顯示了遵守 Java SE 7 規範的典型的 JVM 核心內部元件。 上圖顯示的元件分兩個章節解釋。第一章討論針對每個執行緒建立的元件,第二章節討論了執行緒無關元件。 執行緒 JVM 系統執行緒

Java虛擬機器----JVM記憶體結構

http://www.cnblogs.com/smyhvae/p/4748392.htm   主要內容如下: JVM啟動流程 JVM基本結構 記憶體模型 編譯和解釋執行的概念   一、JVM啟動流程: JVM啟動時,是由java命令/javaw命令來啟

《深入理解java虛擬機器》筆記2——GC演算法與記憶體分配策略

2019大三的寒假計劃——利用在公司每天早起的時間讀書,第一本是周志明老師的《深入理解Java虛擬機器——JVM高階特性與最佳實踐》,這一系列是通過對原文的拜讀與自己理解加上網路上的資料文章整理出的讀書筆記。 說起垃圾收集(Garbage Collection, GC),想

Java虛擬機器03----常用JVM配置引數

本文主要內容: Trace跟蹤引數 堆的分配引數 棧的分配引數 零、在IDE的後臺列印GC日誌: 既然學習JVM,閱讀GC日誌是處理Java虛擬機器記憶體問題的基礎技能,它只是一些人為確定的規則,沒有太多技術含量。 既然如此,那麼在I

Java虛擬機器----JVM常見問題總結

【宣告】  歡迎轉載,但請保留文章原始出處→_→  【正文】 宣告:本文只是做一個總結,有關jvm的詳細知識可以參考本人之前的系列文章,尤其是那篇:Java虛擬機器詳解04----GC演算法和種類。那篇文章和本文是面試時的重點。 面試必問關鍵詞:JVM垃圾回

Java虛擬機器----常用JVM配置引數

原文地址:http://www.cnblogs.com/smyhvae/p/4736162.html 本文主要內容: Trace跟蹤引數堆的分配引數棧的分配引數 零、在IDE的後臺列印GC日誌: 既然學習JVM,閱讀GC日誌是處理Java虛擬機器記憶體問題的

Java虛擬機器(一)------簡介

  本系列部落格我們將以當前預設的主流虛擬機器HotSpot 為例,詳細介紹 Java虛擬機器。以 JDK1.7 為主,同時介紹與 JDK1.8 的不同之處,通過Oracle官網以及各種文獻進行整理,並加以驗證,力求保證這塊知識的正確性,完整性。   以下是本系列部落格參考的相關文件:   ①、JDK1.

Java虛擬機器(二)------執行時記憶體結構

  首先通過一張圖瞭解 Java程式的執行流程:      我們編寫好的Java原始碼程式,通過Java編譯器javac編譯成Java虛擬機器識別的class檔案(位元組碼檔案),然後由 JVM 中的類載入器載入編譯生成的位元組碼檔案,載入完畢之後再由 JVM 執行引擎去執行。在載入完畢到執行過程中,J

Java虛擬機器(三)------垃圾回收

  如果對C++這門語言熟悉的人,再來看Java,就會發現這兩者對垃圾(記憶體)回收的策略有很大的不同。   C++:垃圾回收很重要,我們必須要自己來回收!!!   Java:垃圾回收很重要,我們必須交給系統來幫我們完成!!!   我想這也能看出這兩門語言設計者的心態吧,總之,Java和C++之間有一堵

Java虛擬機器(四)------垃圾收集器

  上一篇部落格我們介紹了Java虛擬機器垃圾回收,介紹了幾種常用的垃圾回收演算法,包括標記-清除,標記整理,複製等,這些演算法我們可以看做是記憶體回收的理論方法,那麼在Java虛擬機器中,由誰來具體實現這些方法呢?   沒錯,就是本篇部落格介紹的內容——垃圾收集器。 1、垃圾收集

Java虛擬機器(五)------JVM引數(持續更新)

  JVM引數有很多,其實我們直接使用預設的JVM引數,不去修改都可以滿足大多數情況。但是如果你想在有限的硬體資源下,部署的系統達到最大的執行效率,那麼進行相關的JVM引數設定是必不可少的。下面我們就來對這些JVM引數進行詳細的介紹。   JVM引數主要分為以下三種(可以根據書寫形式來區分): 1、標準引

Java虛擬機器(六)------記憶體分配

  我們說Java是自動進行記憶體管理的,所謂自動化就是,不需要程式設計師操心,Java會自動進行記憶體分配和記憶體回收這兩方面。   前面我們介紹過如何通過垃圾回收器來回收記憶體,那麼本篇部落格我們來聊聊如何進行分配記憶體。   物件的記憶體分配,往大方向上講,就是堆上進行分配(但也有可能經過JIT編譯

Java虛擬機器(七)------虛擬機器監控分析工具(1)——命令列

  通過前面的幾篇部落格,我們介紹了Java虛擬機器的記憶體分配以及記憶體回收等理論知識,瞭解這些知識對於我們在實際生產環境中提高系統的執行效率是有很大的幫助的。但是話又說回來,在實際生產環境中,線上專案正在執行,我們怎麼去監控虛擬機器執行效率?又或者線上專案發生了OOM,異常堆疊資訊,我們又怎麼去抓取,然後

Java虛擬機器(八)------虛擬機器監控分析工具(2)——視覺化

  上篇部落格我們介紹了虛擬機器監控和分析命令列工具,由於其不夠直觀,不是很容易排查問題,那麼本篇部落格我們就來介紹幾個視覺化工具。 1、JConsole   JConsole(Java Monitoring and Management Console)是一款基於 JMX 的視覺化監視和管理的工具。它管

Java虛擬機器(十)------類載入過程

  在上一篇文章中,我們詳細的介紹了Java類檔案結構,那麼這些Class檔案是如何被載入到記憶體,由虛擬機器來直接使用的呢?這就是本篇部落格將要介紹的——類載入過程。 1、類的生命週期   類從被載入到虛擬機器記憶體開始,到卸載出記憶體為止,其宣告週期流程如下:      上

Java虛擬機器(十一)------雙親委派模型

  在上一篇部落格,我們介紹了類載入過程,包括5個階段,分別是“載入”,“驗證”,“準備”,“解析”,“初始化”,如下圖所示:        本篇部落格,我們來介紹Java虛

深入理解Java虛擬機器系列——JVM的GC理論

GC的概念    GC:Garbage Collection 垃圾收集。這裡所謂的垃圾指的是在系統執行過程當中所產生的一些無用的物件,這些物件佔據著一定的記憶體空間,如果長期不被釋放,可能導致OOM(堆溢位)。記憶體區域中的程式計數器、虛擬機器棧、本地方法棧這3個區域隨著執行