深刻理解Java虛擬機(筆記,更新中)

Java 運行時數據區域

1 程序計數器:是一塊較小的內存空間,能夠當作,當前線程執行字節碼文件的行號指示器,如圖可知,這部分是不共享的數據區,若是執行的是Java代碼,這個計算器記錄的是 正在執行的虛擬機字節碼指令地址,若是執行的是Native方法,這個計數器的值爲空,該區域是惟一沒有內存泄漏問題的區域算法

2 虛擬機棧:也是線程私有的,生命週期與線程相同,描述的是Java方法執行的內存模型,每一個方法執行的同時也會建立一個棧幀(方法運行時的基本數據結構),每一個方法從調用到執行完成的過程,就對應着一個棧幀從入棧到出棧的過程,該區域規定了兩種異常數組

a:線程請求的深度大於虛擬機所容許的深度,StackOverFlowError數據結構

b:若是虛擬機能夠動態擴展(大部分虛擬機均可以)若是擴展時,沒法申請到足夠的內存,OutOfMemoryError異常併發

3 本地方法棧:線程私有,與虛擬機棧十分的相似,不一樣點在與,該區域是用來請求Native方法服務的,有的虛擬機甚至將二者合二爲一,異常與2一致StackOverFlowError  OutOfMemoryErroroop

4 堆:堆是Java虛擬機管理的最大一塊的內存空間,被全部線程共享的一塊內存區域,此區域最大的做用就是用來存放對象的實例(new xxx),是Java垃圾回收機制主要管理的區域,也叫GC堆(垃圾堆),從內存回收的角度來看,Java堆能夠分爲新生代,老年代,若是在堆中沒有內存來分配實例,且沒法再擴展將會拋出OutOfMemoryError異常佈局

5 方法區:方法區也是線程共享的區域,它用於存儲已經被線程加載的類信息,常量,靜態常量,即時編譯器編譯過的代碼,Java虛擬機規範把方法區描述成堆的一個邏輯,可是它卻有一個別名,叫作Non-Heap(非堆),目的應該是要與堆分開來,當方法區沒法知足內存的分配時,會跑出OutOfMemoryErrorspa

6 運行常量池:是方法區的一部分,class文件中,除了有類的版本,字段,方法,接口描述等,還有一項信息,常量池,用於存放編譯期生成的各類字面量和符號的應用,這部分的內容加載進入方法區在常量池中存放,既然是方法區的一部分,那麼也受到方法區的限制,當方法區沒法知足內存的分配時,會跑出OutOfMemoryError線程

字面量:int i=3 3就是這個int類型的字面量  String s=「abc」 abc就是這個s的字面量指針

hotsport虛擬機對象探祕

對象的建立

1 虛擬機遇到一個new指令對象

2 檢查指令的參數,是否在常量池中找到一個類的符號應用,而且檢查這個符號表明的類是否已經被加載過了,沒有,先加載類(TODO,類加載後續再說)有,分配內存

3 爲新生對象分配內存,對象所需的內存大小在類加載以後,徹底能夠肯定(TODO)

內存絕對規整:用過放一邊,空閒放一邊,中間有指針做爲分界點的指示器,那麼分配的內存僅僅是把指針向空閒那一邊挪動一段與對象大小相等的距離,稱爲指針碰撞

內存不規整:已用和空閒的相互交錯,沒有辦法指針碰撞,虛擬機就要維護一個列表,記錄一下,哪塊是可用,哪塊不可用,分配的時候,從列表上找一塊足夠大的內存分配稱爲空閒列表

問題來了?併發的狀況下,給A分配內存,還沒指過去,B就佔用了怎麼辦?

方法一:分配內存空間也作同步處理+失敗重試機制,保證原子性

方法二:把內存分配的動做,按照線程,分在不一樣的空間之中,即,每一個線程先分一塊

4 內存分配完了以後,虛擬機將分配到的內存所有清零,這樣就保證了字段實例,在Java代碼中,不用初始化,就可使用

5 接下來,虛擬機要對對象作一些設置,好比,這個對象,是哪一個類的實例,對象的哈希碼,這些信息存放在對象頭上

6 以上動做完成以後,從虛擬機的視角看,一個新的對象就產生了,但從Java視角看,對象建立纔剛剛開始,init方法尚未執行,對象中全部的字段仍是0,完成以後,纔是一個真正的對象

對象的內存佈局

在hotspot虛擬機中,分爲三個區域:對象頭、實例數據、對齊填充

對象頭

  • 運行時數據 
    • 內容舉例: 
      • 哈希碼HashCode
      • GC分代年齡
      • 鎖狀態標識
      • 線程持有的鎖等
    • 長度(未開啓壓縮指針下):32 or 64 位的虛擬機中分別爲 32bit or 64 bit
    • 官方名稱:Mark Word
    • 非固定數據結構: 
      • 考慮到存儲效率,在極小空間內存儲更多信息,長度是不肯定的
  • 類型指針 
    • 做用:對象指向它的類元數據的指針,JVM經過此來肯定該對象是哪一個類的實例 
      • 並不是全部JVM的實現都須要這個指針,也就是對象的元數據查找並不必定要通過對象自己

注意:對於數組而言,對象頭中還會記錄數組的長度。JVM能夠經過對象的元數據信息肯定Java對象的大小。但從數組對象的元數據中是沒法獲取數組大小的。

實例數據

  • 做用:對象真正存儲的有效信息,也是在代碼中所定義的各類類型的字段內容
  • 內容:不管是從父類繼承下來的,仍是在子類中定義的,都要記錄
  • 存儲順序(HotSpot) 
    • 策略:同寬度者分配到一塊兒
    • 具體分配方式: 
      • longs/doubles
      • ints
      • shorts/chars
      • bytes/booleans
      • oop(Ordinary Object Pointers)

對齊填充部分

  • 做用:佔位符
  • 並不是必須存在
  • HotSpot要求對象大小是8字節的整數倍 
  • 頭部分正好符合要求,當實例數據部分沒有對齊時,就須要經過對齊補充來補全

  新老生代

  新生代
  堆中   存放那些很快被GC回收掉的/不是特別大的對象   採用複製算法
  3個區(較大的Eden,兩個較小的Survivor Eden:Survivor = 8:1)
  發生在新生代的GC爲Minor GC
  JVM 沒法爲一個新的對象分配空間時會觸發 Minor GC
  在Minor GC時會將新生代中還存活着的對象複製進一個Survivor中,而後對Eden和另外一個Survivor進行清理。因此,日常可用的新生代大小    爲Eden的大小+一個Survivor的大小。
  老年代
  堆中 老年代則是存放那些在程序中經歷了好幾回回收仍然還活着或者特別大的對象
  採用 標記-清除算法,這兩個算法主要看虛擬機採用的哪一個收集器
  在老年代中的GC則爲Major GC
  Full GC 是清理整個堆空間—包括年輕代和老年代。
  永久代
  JVM的方法區,也被稱爲永久代。在這裏都是放着一些被虛擬機加載的類信息,靜態變量,常量等數據。這個區中的東西比老年代和新生代更    不容易回收。

  那麼什麼狀況下,新生代的對象會進入老年代呢?   1 新生代存活的對象大於Survivor的大小時,這時一個Survivor裝不下它們,那麼它們就會進入老年代。   2 若是設置了-XX:PretenureSizeThreshold3M 那麼大於3M的對象就會直接就進入老年代。   3 在新生代的每一次Minor GC 都會給在新生代中的對象+1歲,默認到15歲時就會重新生代進入老年代,能夠經過-XX:         MaxTenuringThreshold來設置這個臨界點。老年代中的對象比新生代中的對象不易回收許多。

相關文章
相關標籤/搜索