1. 程式人生 > >JVM虛擬機器記憶體

JVM虛擬機器記憶體

JVM執行時記憶體組成分為一些執行緒私有的,其他的是執行緒共享的。

執行緒私有

  • 程式計數器:當前執行緒所執行的位元組碼的行號指示器。
  • Java虛擬機器棧:java方法執行的記憶體模型,每個方法被執行時都會建立一個棧幀,儲存區域性變量表,操作棧,動態連結,方法出口等資訊。每個執行緒都有自己獨立的棧空間,執行緒棧只儲存基本型別和物件地址,方法中區域性變數存放線上程空間中。
  • 本地方法棧:Native方法服務,在hotspot虛擬機器中和java虛擬機器棧合二為一。

執行緒共享

  • java堆:存放物件實力,幾乎所有的物件例項及其屬性都在這裡分配記憶體。此外,jvm在記憶體新生代eden space中開闢了一塊執行緒私有的區域,稱作TLAB(Thread Local Allocation Buffer),也是每個執行緒的緩衝區,預設設定為佔用Eden space的1%。在編譯器做逃逸分析的時候,根據分析結果,決定是在棧上還是在堆上分配記憶體,如果在堆上則再分析是否在TLAB上分配記憶體。在TLAB上分配由於是執行緒私有的,因此沒有鎖的開銷,效率較高。
  • 方法區:儲存已經被虛擬機器載入的類資訊,常量,靜態變數,JIT編譯後的程式碼等資料,也稱作永久代。java7已經把字串常量池移動到堆中,在呼叫String的intern方法時,如果堆中存在相同的字串物件,會直接儲存物件的引用,不會重新建立物件。
  • 直接記憶體:NIO,Native函式直接分配的堆外記憶體。DirectBuffer引用會使用此部分記憶體。

記憶體分配過程

  1. 編譯器通過逃逸分析,確定物件是在棧上分配還是堆上分配。如果在堆上分配直接進入步驟4。
  2. 如果是tlab_top + size <= tlab_end,則在TLAB上直接分配物件並增加tlab_top的值。如果現有TLAB不足存放當前物件則進入步驟3。
  3. 重新申請一個TLAB,並再次嘗試存放當前物件,如果放不下,則進入步驟4。
  4. 在Eden區加鎖(此區多執行緒共享),如果eden_top + size <= eden_end,則將物件存放在eden區,增加eden_top的值,如果eden區不足以存放,則進入步驟5。
  5. 執行一次YGC。
  6. 經過YGC後,如果eden還放不下物件,則直接分配到老年代。

物件訪問

  • 控制代碼訪問:通過棧本地變量表,找到堆中物件例項指標,根據指標在堆中找到例項資料,在方法區中找到物件型別資料。
  • 直接指標:通過棧本地變量表,找到堆中物件例項指標,物件例項指標中儲存物件例項資料,在方法區中找到物件型別資料。

物件建立

  • 物件在eden完成記憶體分配。
  • eden滿了,在建立物件,會因為申請不到空間,觸發minor gc,堆eden+s 區進行回收。
  • 進行minor gc時,eden區不能被回收的物件進入s區,另一個s區中不能被gc回收的物件也會進入這個s區,始終保證一個s區空置。
  • 如果s區滿了,這些物件會被copy到old區,或者s區沒有滿,但是有些物件足夠old了,會被放入old區。
  • old區滿了之後,進行full gc。

記憶體溢位

在JVM申請記憶體的過程中,會遇到無法申請到足夠記憶體的情況,從而導致記憶體溢位。

  • 虛擬機器棧和本地方法區棧溢位:statkoverflowerror:執行緒請求的棧深度大於虛擬機器所允許的最大深度,迴圈遞迴會觸發這種OOM。outfomemoryerror:虛擬機器在擴充套件棧時無法申請到足夠的記憶體空間,一般可以通過不停建立執行緒觸發這種OOM。
  • java堆溢位:建立大量物件並且物件生命週期很長情況時,會引發outofmemoryerror。
  • 方法區溢位:方法區存放class等元資料資訊,如果產生大量的類(如CGLIB),會引發這種記憶體溢位,outofmemoryerror:permgen space,在使用hibernate等動態生成類框架時會引起這種情況。

垃圾回收和系統吞吐量

  • 吞吐量:指的是單位時間內完成的工作量的度量。
  • 響應時間:是提交請求和返回該請求的響應之間使用的時間。

通常平均響應時間越短,系統吞吐量越大,平均響應時間越長,吞吐量越小。

  • 並行垃圾回收器關注的是吞吐量,會在一定程度上犧牲響應時間。可能某次請求會特別慢。
  • 併發垃圾回收器關注的是請求響應時間,會犧牲吞吐量。會盡量使得每次請求時間維持在差不多水平。

對於CMS觸發full gc的情況:

  • old區使用到一定比例時觸發,通過cmsinitiatingoccupancyfacti