JVM相關知識梳理

 

 

1.程序計數器:一個較小的內存,相當於線程運行指向的代碼行數的指示器,用於循環、跳轉、異常處理、線程恢復等功能,是一個私有內存;

2.虛擬機棧:是方法執行的內存模板,像局部變量、操作數棧、方法出口等;

3.方法區:用於存放已經加載的類信息、常量、靜態變量、即時編譯後的代碼等信息

4. 堆:是虛擬機中最大的內存,是一個程序共享內存區域,在虛擬機啓動時創建,用於存放對象,也是GC管理的重要區域;

java虛擬機主要分爲以下一個區:

1.方法區:
1.
有時候也成爲永久代,在該區內很少發生垃圾回收,但是並不代表不發生GC,在這裏進行的GC主要是對方法區裏的常量池和對類型的卸載
2.
方法區主要用來存儲已被虛擬機加載的類的信息、常量、靜態變量和即時編譯器編譯後的代碼等數據。
3.
該區域是被線程共享的。
4.
方法區裏有一個運行時常量池,用於存放靜態編譯產生的字面量和符號引用。該常量池具有動態性,也就是說常量並不一定是編譯時確定,運行時生成的常量也會存在這個常量池中。

2.虛擬機棧:
1.
虛擬機棧也就是我們平常所稱的棧內存,它爲java方法服務,每個方法在執行的時候都會創建一個棧幀,用於存儲局部變量表、操作數棧、動態鏈接和方法出口等信息。
2.
虛擬機棧是線程私有的,它的生命週期與線程相同。
3.
局部變量表裏存儲的是基本數據類型、returnAddress類型(指向一條字節碼指令的地址)和對象引用,這個對象引用有可能是指向對象起始地址的一個指針,也有可能是代表對象的句柄或者與對象相關聯的位置。局部變量所需的內存空間在編譯器間確定
4.
操作數棧的作用主要用來存儲運算結果以及運算的操作數,它不同於局部變量表通過索引來訪問,而是壓棧和出棧的方式
5.
每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程中的動態連接.動態鏈接就是將常量池中的符號引用在運行期轉化爲直接引用。

3本地方法棧
本地方法棧和虛擬機棧類似,只不過本地方法棧爲Native方法服務。

4
java
堆是所有線程所共享的一塊內存,在虛擬機啓動時創建,幾乎所有的對象實例都在這裏創建,因此該區域經常發生垃圾回收操作。

程序計數器
內存空間小,字節碼解釋器工作時通過改變這個計數值可以選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理和線程恢復等功能都需要依賴這個計數器完成。該內存區域是唯一一個java虛擬機規範沒有規定任何OOM情況的區域。

如何讓新生對象預留再年輕代?

1) 增大年輕代的空間

2) 設置年輕代的使用率達到一定比例,再進入年老代

 

堆大小設置
JVM 中最大堆大小有三方面限制:相關操作系統的數據模型(32-bt還是64-bit)限制;系統的可用虛擬內存限制;系統的可用物理內存限制。32位系統下,一般限制在1.5G~2G64爲操作系統對內存無限制。我在Windows Server 2003 系統,3.5G物理內存,JDK5.0下測試,最大可設置爲1478m
典型設置:

1.      java -Xmx3550m-Xms3550m -Xmn2g -Xss128k 
- Xmx3550m
 
:設置JVM最大可用內存3550M
-Xms3550m 
:設置JVM促使內存爲3550m。此值可以設置與-Xmx相同,以避免每次垃圾回收完成後JVM重新分配內存。
-Xmn2g :設置年輕代大小爲2G整個JVM內存大小=年輕代大小 + 年老代大小 + 持久代大小 持久代一般固定大小爲64m,所以增大年輕代後,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8
-Xss128k 
:設置每個線程的堆棧大小。JDK5.0以後每個線程堆棧大小爲1M,以前每個線程堆棧大小爲256K。更具應用的線程所需內存大小進行調整。在相同物理內存下,減小這個值能生成更多的線程。但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右。

2.      java -Xmx3550m-Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4-XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0 
-XX:NewRatio=4
 :
設置年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。設置爲4,則年輕代與年老代所佔比值爲14,年輕代佔整個堆棧的1/5
-XX:SurvivorRatio=4 
設置年輕代中Eden區與Survivor區的大小比值。設置爲4,則兩個Survivor區與一個Eden區的比值爲2:4,一個Survivor區佔整個年輕代的1/6
-XX:MaxPermSize=16m :設置持久代大小爲16m
-XX:MaxTenuringThreshold=0 設置垃圾最大年齡。如果設置爲0的話,則年輕代對象不經過 Survivor區,直接進入年老代 。對於年老代比較多的應用,可以提高效率。如果將此值設置爲一個較大值,則年輕代對象會在Survivor區進行多次複製,這樣可以增加對象再年輕代的存活時間,增加在年輕代即被回收的概論。

 

 

 

回收器的選擇
JVM給了三種選擇:串行收集器、並行收集器、併發收集器 ,但 是串行收集器只適用於小數據量的情況,所以這裏的選擇主要針對並行收集器和併發收集器。默認情況下,JDK5.0以前都是使用串行收集器,如果想使用其他 收集器需要在啓動時加入相應參數。JDK5.0以後,JVM會根據當前系統配置 進行判斷。

1.    吞吐量優先 的並行收集器
如上文所述,並行收集器主要以到達一定的吞吐量爲目標,適用於科學技術和後臺處理等。
型配置 

  1. java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 
    -XX:+UseParallelGC
     :選擇垃圾收集器爲並行收集器。 此配置僅對年輕代有效。即上述配置下,年輕代使用併發收集,而年老代仍舊使用串行收集。
    -XX:ParallelGCThreads=20 :配置並行收集器的線程數,即:同時多少個線程一起進行垃圾回收。此值最好配置 與處理器數目相等。

  2. java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
    -XX:+UseParallelOldGC 
    :配置年老代垃圾收集方式爲並行收集。JDK6.0支持對年老代並行收集。

  3. java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC  -XX:MaxGCPauseMillis=100
    -XX:MaxGCPauseMillis=100 :
     設置每次年輕代垃圾回收的最長時間,如果 無法滿足此時間,JVM會自動調整年輕代大小,以滿足此值。

  4. java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC  -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
    -XX:+UseAdaptiveSizePolicy
     : 設置此選項後,並行收集器會自動選擇年輕代區大小和相應的Survivor區比例,以達到目標系統規定的最低相應時間或者收集頻率等,此值建議使用並行收 集器時,一直打開。

2.    響應時間優先 的併發收集器
如上文所述,併發收集器主要是保證系統的響應時間,減少垃圾收集時的停頓時間。適用於應用服務器、電信領域等。
典型配置 

0.     java -Xmx3550m -Xms3550m -Xmn2g -Xss128k-XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
 :設置年老代爲併發收集。測試中配置這個以後,-XX:NewRatio=4的配置失效了,原因不明。所以,此時年輕代大小最好用-Xmn設置。
-XX:+UseParNewGC :
設置年輕代爲並行收集。可與CMS收集同時使用。JDK5.0以上,JVM會根據系統配置自行設置,所以無需再設置此值。

1.     java -Xmx3550m -Xms3550m -Xmn2g -Xss128k-XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection 
-XX:CMSFullGCsBeforeCompaction 由於併發收集器不對內存空間進行壓縮、整理,所以運行一段時間以後會產生碎片,使得運行效率降低。此值設置運行多少次GC以後對內存空間進行壓縮、整理。
-XX:+UseCMSCompactAtFullCollection :打開對年老代的壓縮。可能會影響性能,但是可以消除碎片

1.    輔助信息
JVM提供了大量命令行參數,打印信息,供調試使用。主要有以下一些:

1.     -XX:+PrintGC
輸出形式[GC 118250K->113543K(130112K),0.0094143 secs]

               [Full GC 121376K->10414K(130112K), 0.0650971 secs]

2.     -XX:+PrintGCDetails
輸出形式[GC [DefNew: 8614K->781K(9088K),0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]

               [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured:112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K),0.0436268 secs]

3.     -XX:+PrintGCTimeStamps -XX:+PrintGCPrintGCTimeStamps可與上面兩個混合使用
輸出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]

4.     -XX:+PrintGCApplicationConcurrentTime: 打印每次垃圾回收前,程序未中斷的執行時間。可與上面混合使用
輸出形式:Application time: 0.5291524 seconds

5.     -XX:+PrintGCApplicationStoppedTime :打印垃圾回收期間程序暫停的時間。可與上面混合使用
輸出形式:Total time for which application threads were stopped:0.0468229 seconds

6.     -XX:PrintHeapAtGC :打印GC前後的詳細堆棧信息
輸出形式:
34.702: [GC {Heap before gc invocations=7:
 def new generation   total 55296K, used 52568K [0x1ebd0000,0x227d0000, 0x227d0000)
eden space 49152K,  99% used [0x1ebd0000, 0x21bce430,0x21bd0000)
from space 6144K,  55% used [0x221d0000, 0x22527e10,0x227d0000)
  to   space 6144K,   0% used [0x21bd0000, 0x21bd0000,0x221d0000)
 tenured generation   total 69632K, used 2696K [0x227d0000,0x26bd0000, 0x26bd0000)
the space 69632K,   3% used [0x227d0000, 0x22a720f8,0x22a72200, 0x26bd0000)
 compacting perm gen  total 8192K, used 2898K [0x26bd0000,0x273d0000, 0x2abd0000)
   the space 8192K,  35% used [0x26bd0000, 0x26ea4ba8,0x26ea4c00, 0x273d0000)
    ro space 8192K,  66% used [0x2abd0000, 0x2b12bcc0,0x2b12be00, 0x2b3d0000)
    rw space 12288K,  46% used [0x2b3d0000, 0x2b972060,0x2b972200, 0x2bfd0000)
34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs]55264K->6615K(124928K)Heap after gc invocations=8:
 def new generation   total 55296K, used 3433K [0x1ebd0000,0x227d0000, 0x227d0000)
eden space 49152K,   0% used [0x1ebd0000, 0x1ebd0000,0x21bd0000)
  from space 6144K,  55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)
  to   space 6144K,   0% used [0x221d0000, 0x221d0000,0x227d0000)
 tenured generation   total 69632K, used 3182K [0x227d0000,0x26bd0000, 0x26bd0000)
the space 69632K,   4% used [0x227d0000, 0x22aeb958,0x22aeba00, 0x26bd0000)
 compacting perm gen  total 8192K, used 2898K [0x26bd0000, 0x273d0000,0x2abd0000)
   the space 8192K,  35% used [0x26bd0000, 0x26ea4ba8,0x26ea4c00, 0x273d0000)
    ro space 8192K,  66% used [0x2abd0000, 0x2b12bcc0,0x2b12be00, 0x2b3d0000)
    rw space 12288K,  46% used [0x2b3d0000, 0x2b972060,0x2b972200, 0x2bfd0000)
}
, 0.0757599 secs]

7.     -Xloggc:filename :與上面幾個配合使用,把相關日誌信息記錄到文件以便分析。

2.    常見配置彙總

0.     堆設置

1.     -Xms :初始堆大小

2.     -Xmx :最大堆大小

3.     -XX:NewSize=n :設置年輕代大小

4.     -XX:NewRatio=n: 設置年輕代和年老代的比值。如:3,表示年輕代與年老代比值爲13,年輕代佔整個年輕代年老代和的1/4

5.     -XX:SurvivorRatio=n :年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。如:3,表示EdenSurvivor=32,一個Survivor區佔整個年輕代的1/5

6.     -XX:MaxPermSize=n :設置持久代大小

1.     收集器設置

0.    -XX:+UseSerialGC :設置串行收集器

1.    -XX:+UseParallelGC :設置並行收集器

2.    -XX:+UseParalledlOldGC :設置並行年老代收集器

3.    -XX:+UseConcMarkSweepGC :設置併發收集器

2.     垃圾回收統計信息

0.    -XX:+PrintGC

1.    -XX:+PrintGCDetails

2.    -XX:+PrintGCTimeStamps

3.    -Xloggc:filename

3.     並行收集器設置

0.    -XX:ParallelGCThreads=n :設置並行收集器收集時使用的CPU數。並行收集線程數。

1.    -XX:MaxGCPauseMillis=n :設置並行收集最大暫停時間

2.    -XX:GCTimeRatio=n :設置垃圾回收時間佔程序運行時間的百分比。公式爲1/(1+n)

4.     併發收集器設置

0.    -XX:+CMSIncrementalMode :設置爲增量模式。適用於單CPU情況。

1.    -XX:ParallelGCThreads=n :設置併發收集器年輕代收集方式爲並行收集時,使用的CPU數。並行收集線程數。

四、調優總結

1.    年輕代大小選擇

1.      響應時間優先的應用 可能設大,直到接近系統的最低響應時間限制 (根據實際情況選擇)。在此種情況下,年輕代收集發生的頻率也是最小的。同時,減少到達年老代的對象。

2.     吞吐量優先的應用 :儘可能的設置大,可能到達Gbit的程度。因爲對響應時間沒有要求,垃圾收集可以並行進行,一般適8CPU以上的應用。

2.    年老代大小選擇

0.    響應時間優先的應用 :年老代使用併發收集器,所以其大小需要小心設置,一般要考慮併發會話率話持續時間 等一些參數。如果堆設置小了,可以會造成內存碎片、高回收頻率以及應用暫停而使用傳統的標記清除方式;如果堆大了,則需要較長的收集時間。最優化的方案,一般需要參考以下數據獲得:

1.     併發垃圾收集信息

2.     持久代併發收集次數

3.     傳統GC信息

4.     花在年輕代和年老代回收上的時間比例

減少年輕代和年老代花費的時間,一般會提高應用的效率

1.    吞吐量優先的應用 :一般吞吐量優先的應用都有一個很大的年輕代和一個較小的年老代。原因是,這樣可以儘可能回收掉大部分短期對象,減少中期的對象,而年老代盡存放長期存活對象。

3.    較小堆引起的碎片問題
因爲年老代的併發收集器使用標記、清除算法,所以不會對堆進行壓縮。當收集器回收時,他會把相鄰的空間進行合併,這樣可以分配給較大的對象。但是,當堆空間較小時,運行一段時間以後,就會出現碎片,如果併發收集器找不到足夠的空間,那麼併發收集器將會停止,然後使用傳統的標記、清除方式進行回收。如果出現碎片,可能需要進行如下配置:

0.    -XX:+UseCMSCompactAtFullCollection :使用併發收集器時,開啓對年老代的壓縮。

1.    -XX:CMSFullGCsBeforeCompaction=0 :上面配置開啓的情況下,這裏設置多少次Full GC後,對年老代進行壓縮

整個堆大小=年輕代大小 +年老代大小 ,而非整個堆大小=年輕代大小 + 年老代大小 + 持久代大小

 

 

 

JVM垃圾回收算法
標記-清除算法:首先標記出所有需要回收的對象,在標記完成後統一回收所有被標記的對象。
複製算法:將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當一塊內存用完了,將還存另外一塊上面,然後在把已使用過的內存空間一次清理掉。
標記-整理算法:標記過程與“標記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所一端移動,然後直接清理掉端邊界以外的內存。
分代收集算法:一般是把Java堆分爲新生代和老年代,根據各個年代的特點採用最適當的收集算法。新生代都發現有大批對象死去,選用複製算法。老年代中因爲對象存活率高,必須使用“標記-清理”或“標記-整理”算法來進行回收。

JVM調優
查看堆空間大小分配(年輕代、年老代、持久代分配)
垃圾回收監控(長時間監控回收情況)
線程信息監控:系統線程數量
線程狀態監控:各個線程都處在什麼樣的狀態下
線程詳細信息:查看線程內部運行情況,死鎖檢查
CPU
熱點:檢查系統哪些方法佔用了大量CPU時間
內存熱點:檢查哪些對象在系統中數量最大

 

如和判斷一個對象是否存活?(或者GC對象的判定方法)

判斷一個對象是否存活有兩種方法:
1. 
引用計數法
所謂引用計數法就是給每一個對象設置一個引用計數器,每當有一個地方引用這個對象時,就將計數器加一,引用失效時,計數器就減一。當一個對象的引用計數器爲零時,說明此對象沒有被引用,也就是死對象”,將會被垃圾回收.
引用計數法有一個缺陷就是無法解決循環引用問題,也就是說當對象A引用對象B,對象B又引用者對象A,那麼此時A,B對象的引用計數器都不爲零,也就造成無法完成垃圾回收,所以主流的虛擬機都沒有采用這種算法。

2.可達性算法(引用鏈法)
該算法的思想是:從一個被稱爲GC Roots的對象開始向下搜索,如果一個對象到GC Roots沒有任何引用鏈相連時,則說明此對象不可用。
java中可以作爲GC Roots的對象有以下幾種:

·      虛擬機棧中引用的對象

·      方法區類靜態屬性引用的對象

·      方法區常量池引用的對象

·      本地方法棧JNI引用的對象

雖然這些算法可以判定一個對象是否能被回收,但是當滿足上述條件時,一個對象比不一定會被回收。當一個對象不可達GC Root時,這個對象並 
不會立馬被回收,而是出於一個死緩的階段,若要被真正的回收需要經歷兩次標記
如果對象在可達性分析中沒有與GC Root的引用鏈,那麼此時就會被第一次標記並且進行一次篩選,篩選的條件是是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法或者已被虛擬機調用過,那麼就認爲是沒必要的。
如果該對象有必要執行finalize()方法,那麼這個對象將會放在一個稱爲F-Queue的對隊列中,虛擬機會觸發一個Finalize()線程去執行,此線程是低優先級的,並且虛擬機不會承諾一直等待它運行完,這是因爲如果finalize()執行緩慢或者發生了死鎖,那麼就會造成F-Queue隊列一直等待,造成了內存回收系統的崩潰。GC對處於F-Queue中的對象進行第二次被標記,這時,該對象將被移除即將回收集合,等待回收。

13.簡述java垃圾回收機制?

java中,程序員是不需要顯示的去釋放一個對象的內存的,而是由虛擬機自行執行。在JVM中,有一個垃圾回收線程,它是低優先級的,在正常情況下是不會執行的,只有在虛擬機空閒或者當前堆內存不足時,纔會觸發執行,掃面那些沒有被任何引用的對象,並將它們添加到要回收的集合中,進行回收。

java中垃圾收集的方法有哪些?

1.     標記-清除:
這是垃圾收集算法中最基礎的,根據名字就可以知道,它的思想就是標記哪些要被回收的對象,然後統一回收。這種方法很簡單,但是會有兩個主要問題:1.效率不高,標記和清除的效率都很低;2.會產生大量不連續的內存碎片,導致以後程序在分配較大的對象時,由於沒有充足的連續內存而提前觸發一次GC動作。

2.     複製算法:
爲了解決效率問題,複製算法將可用內存按容量劃分爲相等的兩部分,然後每次只使用其中的一塊,當一塊內存用完時,就將還存活的對象複製到第二塊內存上,然後一次性清楚完第一塊內存,再將第二塊上的對象複製到第一塊。但是這種方式,內存的代價太高,每次基本上都要浪費一般的內存。
於是將該算法進行了改進,內存區域不再是按照11去劃分,而是將內存劃分爲8:1:1三部分,較大那份內存交Eden區,其餘是兩塊較小的內存區叫Survior區。每次都會優先使用Eden區,若Eden區滿,就將對象複製到第二塊內存區上,然後清除Eden區,如果此時存活的對象太多,以至於Survivor不夠時,會將這些對象通過分配擔保機制複製到老年代中。(java堆又分爲新生代和老年代)

3.     標記-整理
該算法主要是爲了解決標記-清除,產生大量內存碎片的問題;當對象存活率較高時,也解決了複製算法的效率問題。它的不同之處就是在清除對象的時候現將可回收對象移動到一端,然後清除掉端邊界以外的對象,這樣就不會產生內存碎片了。

4.     分代收集 
現在的虛擬機垃圾收集大多采用這種方式,它根據對象的生存週期,將堆分爲新生代和老年代。在新生代中,由於對象生存期短,每次回收都會有大量對象死去,那麼這時就採用複製算法。老年代裏的對象存活率較高,沒有額外的空間進行分配擔保,所以可以使用標記-整理 或者 標記-清除

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章