1. 程式人生 > >《深入理解Java虛擬機器》讀書筆記(JVM常識彙總一)

《深入理解Java虛擬機器》讀書筆記(JVM常識彙總一)

0:JVM的記憶體佈局: (這裡用百度到的圖)


這應該是比較經典的一個JVM記憶體佈局圖。 如圖上:堆和方法區是被執行緒共享的,虛擬機器棧,本地方法棧,程式計數器,這是每個執行緒私有的,即每個執行緒都有1份。 

【1】堆:絕大多數物件都存放在這裡,當然肯定有的物件活的久,壽命長,有的物件活的短,壽命短,所以堆裡的記憶體又可分為:新生代(Young generation)和老年代(Old generation)。  預設 新生代佔 1/3的堆空間,老年代佔2/3的堆空間。   新生代又可分為:

1個Eden(伊甸園,就是物件出生的地方) 和 2個Survivor(from survivor 和 to survivor) 。它們的比例是 8:1: 1 。 但是survivor每次只用1個,留一個作為複製存活物件所用。(複製演算法,下一篇會說) 。 所以新生代可用空間為整個新生代的90%。    

堆是執行緒共享的。

【2】方法區:用來儲存 常量池, 類元資訊 ,方法元資訊 , 在JDK8 後,名字變成了 MetaSpace(元資料區)  ,方法區也是執行緒共享的。

 

【3】本地方法棧 : 類似於虛擬機器棧,只不過這個棧主要是為了Native方法服務。   執行緒私有

 

【4】虛擬機器棧: 執行方法時候,這個方法就會入棧,成為1個棧幀,棧幀(也就是方法)裡面存放著 區域性變量表 操作棧 動態連結, 方法返回地址 等方法資訊。  執行緒私有。

 

【5】程式計數器:

          由於CPU時間片的輪流執行,執行某個執行緒中的指令時候,有時候可能會切換到另一個執行緒,這樣就必然會導致執行緒間的切換,中斷,以及恢復。為了保證這些操作可以不出差錯,前後銜接。 每個執行緒在建立後,都會產生自己的程式計數器和棧幀,程式計數器用來存放執行指令的偏移量和行號指示器等,執行緒在切換,中斷,恢復,執行都需要這個“標識” 。  執行緒私有。

  

1、絕大部分的物件都分配在堆裡,在垃圾收集器工作前,首先要做的就是判斷哪些物件“存活”,哪些物件“死亡”。(即已經不能再被使用獲取到的物件)
 

2、 2種判斷方法:
2.1 : 引用計數演算法:  給物件新增一個引用計數器,每當一個物件引用它時,計數器的值就加1,當引用失效時,計數器的值就減1,當某個物件上的計數器上的值為0時,說明這個物件已經不可能再被使用。(即意味著這個物件成為了即將被回收的物件,這個演算法簡單,而且效率也高,但是Java並沒有採用這個演算法,最主要的原因是因為它很難解決物件之間迴圈引用的問題。程式碼如下:)

public class ReferenceCountingGC {
    /**
     * 《深入理解Java虛擬機器》  -zzm
     */
    public Object instance = null ;
    private static final int _1MB = 1024*1024 ;

    /**
     * 這個陣列(成員屬性)的作用就是佔點記憶體
     */
    private byte[] bigSize = new byte[2 * _1MB];


    public static void main(String[] args) {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        /*通過成員屬性互相引用對方,導致objA和objB的引用計數器都不為0*/
        objA.instance = objB ;
        objB.instance = objA ;


        objA = null ;
        objB = null ;

        //假設在此發生GC,objA和objB被回收了!
        System.gc();
    }
}

接下來是GC打印出來的資訊:

[GC (System.gc()) [PSYoungGen: 7434K->648K(38400K)] 7434K->648K(125952K), 0.4162578 secs] [Times: user=0.48 sys=0.00, real=0.42 secs] 
[Full GC (System.gc()) [PSYoungGen: 648K->0K(38400K)] [ParOldGen: 0K->607K(87552K)] 648K->607K(125952K), [Metaspace: 3207K->3207K(1056768K)], 0.0095349 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 38400K, used 998K [0x00000000d5d00000, 0x00000000d8780000, 0x0000000100000000)
  eden space 33280K, 3% used [0x00000000d5d00000,0x00000000d5df9b20,0x00000000d7d80000)
  from space 5120K, 0% used [0x00000000d7d80000,0x00000000d7d80000,0x00000000d8280000)
  to   space 5120K, 0% used [0x00000000d8280000,0x00000000d8280000,0x00000000d8780000)
 ParOldGen       total 87552K, used 607K [0x0000000081600000, 0x0000000086b80000, 0x00000000d5d00000)
  object space 87552K, 0% used [0x0000000081600000,0x0000000081697f98,0x0000000086b80000)
 Metaspace       used 3262K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 356K, capacity 388K, committed 512K, reserved 1048576K

看列印資訊:PSYoungGen: 7434K->648K(38400K)  新生代使用的記憶體從7434->648K ,意味著虛擬機器將這2個物件回收了。
2.2:可達性分析演算法
       這個演算法的基本思路就是通過一系列的被稱之為“GC Roots”的物件作為起始點,從這些幾點開始向下搜尋,搜尋所通過的路徑稱之為引用鏈,引用鏈上有的物件證明是可達的,有用的物件。當一個物件到GC Roots 沒有任何引用鏈可達,即GC Roots的引用鏈到達不了這個物件,關聯不起來。證明這個物件是不可用的,所以這個物件就會判定為可回收的物件。

在Java語言中,可作為GC Roots的物件包括以下幾種:
1、虛擬機器棧(棧中的本地變量表)中引用的物件
2、方法區中類靜態屬性引用的物件
3、方法區中常量引用的物件
4、本地方法棧中JNI(即一般所說的Native)引用的物件。

注意: 即使在可達性分析演算法中被標記為不可達的物件,也不是“非死不可”。 當它被第一次標記時候,還會被篩選,篩選的條件就是 這個物件是否有必要執行finalize()方法,如果finalize()方法已經被虛擬機器調過或者沒有被這個物件重寫,證明它就沒必要再執行這個方法了,因為任何一個物件的finalize()方法只會被執行一次。它就會被等待下一次標記,至少被標記2次,這個物件才會被真正宣告死亡了。  如果有必要執行finalize()方法,那麼這個物件就可以在finalize()方法裡,重新與引用鏈上的任何一個物件建立關係即可,就可以取消“即將被回收”的標記,換言說,finalize()方法,是一個物件避免回收(擺脫死亡命運的最後一次機會!)