1. 程式人生 > >jvm對象內存分配

jvm對象內存分配

回收 thread 時間 本地 觸發gc 進入 大小 效率 let

一、jvm簡單結構圖

    技術分享圖片

  1、jvm內存對象分配整體流程:

    技術分享圖片

  1、類加載子系統和方法區

    類加載子系統負責從文件系統或者網絡中加載Class信息,加載的類信息存放於一塊稱為方法區的內存空間。除了類的信息外,

    方法區中可能還會存放運行時常量池信息,包括字符串字面量和數字常量(這部分常量信息是Class文件中常量池部分的內存映射)。

  2、java堆

    堆空間在jvm啟動的時候即根據設置(後續 jvm參數調優)創建,是java程序中最主要的內存工作區域。堆空間是所有線程共享的。

    jvm分配對象,一般來說,new個新對象,都是分配在堆空間的。其實這也不是絕對的,存在部分對象的分配是在棧上以及TLAB中。

    對象分配流程圖:

    技術分享圖片

  上圖:如果開啟棧上分配,JVM會先進行棧上分配,如果沒有開啟棧上分配或則不符合條件的則會進行TLAB分配,如果TLAB分配不成功,再嘗試在eden區分配,如果對象滿足了直接進入老年代的條件,那就直接分配在老年代。

①:大對象可直接進入老年代

  大對象是指需要大量的連續的內存空間的java對象,典型的例子就是N長的字符串和數組。大對象在分配的時候,很糾結,很可能會出現提前觸發GC來獲取到足夠的連續的空間來分配他們。JVM提供了一個值參數: -XX:PreteenureSizeThreshold參數,讓對象大於這個參數時直接進入老年代分配。這樣做,可以避免年輕代Eden和兩個Survivor區域發生大量的內存復制,提高效率。

   2.1、棧上分配

在JVM中,堆是線程共享的,因此堆上的對象對於各個線程都是共享和可見的,只要持有對象的引用,就可以訪問堆中存儲的對象數據。虛擬機的垃圾收集系統可以回收堆中不再使用的對象,
但對於垃圾收集器來說,無論篩選可回收對象,還是回收和整理內存都需要耗費時間。 如果確定一個對象的作用域不會逃逸出方法之外,那可以將這個對象分配在棧上,這樣,對象所占用的內存空間就可以隨棧幀出棧而銷毀。在一般應用中,不會逃逸的局部對象所占的比例很大,
如果能使用棧上分配,那大量的對象就會隨著方法的結束而自動銷毀了,無須通過垃圾收集器回收,可以減小垃圾收集器的負載。 JVM允許將線程私有的對象打散分配在棧上,而不是分配在堆上。分配在棧上的好處是可以在函數調用結束後自行銷毀,而不需要垃圾回收器的介入,從而提高系統性能。

  棧上分配的技術基礎:
    一是逃逸分析:逃逸分析的目的是判斷對象的作用域是否有可能逃逸出函數體。關於逃逸分析的問題可以看我另一篇文章:

    二是標量替換:允許將對象打散分配在棧上,比如若一個對象擁有兩個字段,會將這兩個字段視作局部變量進行分配。

只能在server模式下才能啟用逃逸分析,參數-XX:DoEscapeAnalysis啟用逃逸分析,參數-XX:+EliminateAllocations開啟標量替換(默認打開)。選項-XX:+PrintEscapeAnalysis查看逃逸分析的篩選結果。

註意:在部分JDK1.6版本和後續的JDK版本(64位系統)中,-client參數已經不起作用了,Server模式成為唯一

  2.2、TLAB分配

TLAB的全稱是Thread Local Allocation Buffer,即線程本地分配緩存區,這是一個線程專用的內存分配區域。 
由於對象一般會分配在堆上,而堆是全局共享的。因此在同一時間,可能會有多個線程在堆上申請空間。因此,每次對象分配都必須要進行同步(虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性),
而在競爭激烈的場合分配的效率又會進一步下降。JVM使用TLAB來避免多線程沖突,在給對象分配內存時,每個線程使用自己的TLAB,這樣可以避免線程同步,提高了對象分配的效率。 TLAB本身占用eEden區空間,在開啟TLAB的情況下,虛擬機會為每個Java線程分配一塊TLAB空間。參數
-XX:+UseTLAB開啟TLAB,默認是開啟的。TLAB空間的內存非常小,
缺省情況下僅占有整個Eden空間的1%,當然可以通過選項-XX:TLABWasteTargetPercent設置TLAB空間所占用Eden空間的百分比大小。 由於TLAB空間一般不會很大,因此大對象無法在TLAB上進行分配,總是會直接分配在堆上。TLAB空間由於比較小,因此很容易裝滿。比如,一個100K的空間,已經使用了80KB,
當需要再分配一個30KB的對象時,肯定就無能為力了。這時虛擬機會有兩種選擇,第一,廢棄當前TLAB,這樣就會浪費20KB空間;第二,將這30KB的對象直接分配在堆上,保留當前的TLAB,
這樣可以希望將來有小於20KB的對象分配請求可以直接使用這塊空間。實際上虛擬機內部會維護一個叫作refill_waste的值,當請求對象大於refill_waste時,會選擇在堆中分配,
若小於該值,則會廢棄當前TLAB,新建TLAB來分配對象。這個閾值可以使用TLABRefillWasteFraction來調整,它表示TLAB中允許產生這種浪費的比例。默認值為64,
即表示使用約為1
/64的TLAB空間作為refill_waste。默認情況下,TLAB和refill_waste都會在運行時不斷調整的,使系統的運行狀態達到最優。
如果想要禁用自動調整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB,並使用-XX:TLABSize手工指定一個TLAB的大小。 -XX:+PrintTLAB可以跟蹤TLAB的使用情況。一般不建議手工修改TLAB相關參數,推薦使用虛擬機默認行為。

  3、java棧

  每一個java虛擬機線程都有一個私有的java棧,一個線程的java棧在線程創建的時候被創建,java棧中保存著幀信息,

  java棧中保存著局部變量、方法參數,同時和java方法的調用、返回密切相關。

  4、垃圾回收

  垃圾回收系統是java虛擬機的重要組成部分,垃圾回收器可以對方法區、java堆和直接內存進行回收。其中,java堆是垃圾收集器的工作重點。和C/C++不同,java中所有的對象空間釋放都是隱式的,也就是說,java中沒有類似free()或者delete()這樣的函數釋放指定的內存區域。對於不再使用的垃圾對象,垃圾回收系統會在後臺默默工作,默默查找、標識並釋放垃圾對象,完成包括java堆、方法區和直接內存中的全自動化管理。

  5、執行引擎

執行引擎是java虛擬機的最核心組件之一,它負責執行虛擬機的字節碼,現代虛擬機為了提高執行效率,會使用即時編譯(just in time)技術將方法編譯成機器碼後再執行。
Java HotSpot Client VM(-client),為在客戶端環境中減少啟動時間而優化的執行引擎;本地應用開發使用。(如:eclipse)
Java HotSpot Server VM(-server),為在服務器環境中最大化程序執行速度而設計的執行引擎。應用在服務端程序。(如:tomcat)
Java HotSpot Client模式和Server模式的區別當虛擬機運行在
-client模式的時候,使用的是一個代號為C1的輕量級編譯器,
而-server模式啟動的虛擬機采用相對重量級,代號為C2的編譯器.
C2比C1編譯器編譯的相對徹底,服務起來之後,性能更高JDK安裝目錄/jre/lib/(x86、i386、amd32、amd64)/jvm.cfg文件中的內容,
-server和-client哪一個配置在上,執行引擎就是哪一個。如果是JDK1.5版本且是64位系統應用時,-client無效。
--64位系統內容
  -server KNOWN
  -client IGNORE
--32位系統內容
  -server KNOWN
  -client KNOWN
註意:在部分JDK1.6版本和後續的JDK版本(64位系統)中,-client參數已經不起作用了,Server模式成為唯一

二、對象分代結構

  JVM根據對象存活周期的不同,將堆分成幾個不同的結構區域,一般來說是分為:新生代(young)、老年代(old)、永久代(permanent)。

  2.1、為什麽要進行分代

    堆內存是虛擬機管理的最大的一塊內存區域,也是垃圾回收最為頻繁的區域。程序運行時的所有對象實例都在這裏保存。內存分代管理就是為對象的內存分配和銷毀回收提高效率。試想一下,如果不進行分代劃分,所有的對象都放在一起,那些新生的和一些     生命周期很長的都在一起,每次進行GC的時候,都必須得掃描遍歷全部的對象,這個過程是十分耗費資源的,會嚴重的影響GC的效率。

    有了內存的分代,會大大提升垃圾回收的效率。新生成的對象在新生代中分配內存,經過幾次的垃圾回收依然存活下來的就存放到老年代中,永久代中存放靜態屬性以及類信息等。新生代中的對象,生命周期最短,相對的來說GC的頻率就很高。老年代的生命

    周期較長,不需要進行頻繁的內存回收。永久代基本不需要進行回收操作。當然了,回收機制是可以根據不同代的特點來選擇合適的垃圾回收算法的。

  2.2、內存分代

     JVM將內存分為新生代(Eden、Survivor:From和To)、老年代以及永久代。永久代中主要存放靜態變量、常量以及類信息等,基本不進行垃圾回收。新生代和老年代是垃圾回收的主要區域。

     內存的分代示意圖如下:

     技術分享圖片

    2.2.1、新生代

      新生代是程序運行過程中,大部分新生成的對象分配的區域。新生代對象,生命周期很短,垃圾回收相當的頻繁,一般,一次的垃圾回收,能回收掉70-90的空間,回收效率很高。

      新生代分為三塊:一塊較大的Eden區和兩塊較小的Survivor區(兩塊大小相同),默認的大小比率為8:1:1,劃分的目的是因為Hotspot采用復制算法來回收新生代,設置這個比率充分利用內存空間,減少浪費。

      Eden區分配新生成的對象(大對象出外,上面有提及),當Eden區沒有足夠的空間去分配新對象的時候,出發一次minor GC。

      程序運行中,To區域始終需要保持空狀態,對象保存在Eden和From區域。新生成對象首先是存放在Eden區域的,當觸發Minor GC的時候,將Eden中幸存對象拷貝到From區域中並且年齡加1,然後檢查To區域已有

      對象是否可達可用,如若不需要了,對其進行一次清理,幸存的對象年齡加1。將From區域的對象拷貝過來,最後交換From與To的名稱。保持To區域的空。

    2.2.2、老年代

      新生代young中,經過了多次的GC後還能幸存下來的對象(具體的多少次,可進行設置閾值控制,默認15),進入老年代中。老年代中的對象,生命周期長,GC的頻率低,並且回收的效率上也相對較低。

    2.2.3、永久代

      永久代中存儲的是類信息、常量、靜態變量等數據。一般不進行GC。

jvm對象內存分配