關于jvm的幾點要摘

http://www.cnblogs.com/angeldevil/p/3801189.html值得一看


Classic VM和Exact ?VM兩者在GC的時候準確判斷heap上的數據是否還可能被使用。由于使用了準確式內存管理,Exact VM放棄了Classic VM基于handler的對象查找方式(其根本原因是進行GC之后對象將可能會被移動位置,如果將地址123456的對象移動到654321),在沒有明確信息表明內存中哪些數據是reference的前提下,虛擬機是不敢把內存中所有為123456的值改成654321的

hotspot VM ?融合了前兩款VM的優點,添加了新的技術,熱點代碼探測技術



關于垃圾回收機制的深度理解可以閱讀openjdk的源碼,openjdk和oracle/sun的內核虛擬機的源碼90%以上都是公用的


程序計數器:由于每個線程都需要分配一個獨立的程序計數器,是一塊較小的內存空間,可以看做是當前線程執行的字節碼的行號指示器。由于java虛擬機的多線程是通過線程輪流切換并分配處理器的執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對于多核處理器來說就是一個內核)都知會執行一條形成中的指令。因此為了切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間的 計算器互不影響,獨立存儲,“線程私有的內存”

本地方法棧,如果正在執行的是一個java方法,計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是Native方法,這個計數器值側為空。此內存區域是唯一一個在java虛擬機? ? 規范中規范張美又紅規范中沒有規定任何OutOfMemoryError情況的區域。

本地方法棧:? ? ? ? ? ? 顧名思義:為虛擬機使用到的Native方法服務。hotspot直接把本地方法棧和虛擬機棧合二為一。

java堆:java heap

1、虛擬機所管理的內存中最大的一塊

2、所有線程共享的一個內存區域

3、分配對象和數組

4、分代

5、可擴展 -Xmx -Xms

方法區:Method Area? Non-Heap

1、各個線程共享的內存區域

2、存儲類信息、常量、靜態變量、

有種說法將方法區稱為永久代,但這種說法并不確切,因為hotspot虛擬機設計團隊選擇把GC分代擴展至方發區

運行時常量池:方法區的一部分。class分揀中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池Constant pool table,用于存放編譯器生成的各種字面量和符號引用,這部分內容將在加載后進入方法區的運行時常量池中存放

直接內存:

在加入NIO 之后,引入一種基于通道與緩沖區的I/O方式,它可以使用Native函數庫直接分配堆外內存,然后通過一個存儲在java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作。

內存分配方法:指針碰撞法,空閑列表法,

GC過程對應的內存分配法:Compack,Mark-Sweep

為解決同步問題:方法一、實際虛擬機上采用CAS配上失敗嘗試的方式保證更新操作的原子性;

方法二、把內存分配的動作按照線程劃分在不同的空間中進行,

本地線程分配緩沖(Thread Local Allocation Buffer TLAB)通過-XX:+/-UserTLAB參數設定。

引用方式:句柄和直接指針

采用句柄:在java堆中劃分一塊內存作為句柄池,reference中存儲的就是對象的句柄地址

優點:對象移動(考慮到垃圾回收的行為),只要改動reference實例數據的指針地址

缺點

采用直接指針:java堆中,reference中存儲的直接就是對象的地址

優點:速度更快,節省指針定位開銷? ? hotspot采用直接指針訪問

缺點:

實戰OOM(OutOfMemoryError異常)

2.4.2虛擬機棧和本地方法棧溢出:

在hotspot虛擬機中并不區分虛擬機棧和本地方法棧? -Xoss 設置本地方法棧 -Xss參數設置棧大小

2.4.3 方法區和運行時常量池溢出

-XX:PermSize? ? -XX:MaxPermSize

2.4.4? 本機直接內存溢出

-XX:MaxDirectMemorySize 指定

直接內存導致的內存溢出,一個明顯的特征是在Heap Dump文件中不會看見明顯的異常,如果發現OOM之后的Dump文件很小,而程序中又直接或間接使用了NIO,那就可以考慮檢查下是不是這方面的原因

第3章? 垃圾收集器與內存分配策略

程序計數器、虛擬機棧、本地方法棧3個區域伴隨線程的生命周期

內存回收機制:判斷對象對否已死?

3.2.1 引用計數算法:即當有一個地方引用它時,計數器值就+1,當引用失效時,計數器就減1,

優點:實現簡單,判定效率高

主流java虛擬機沒有選用引用計數器算法來管理內存,其主要的原因是它很難解決對象之間互相循環引用的問題。

3.2.2可達性分析算法

基本思路就是通過一些列稱為:“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒喲任何引用鏈相連(用圖論的話來說,就是GC Roots到這個對象不可達)

3.2.5回收方法區

永久代的垃圾回收主要回收兩部分:廢棄常量和無用的類。典型的例子:假如一個字符串“abc”已經進入了常量池中,如果當前系統沒有任何一個String對象叫做“abc”,如果沒有任何String對象引用常量池中的“abc”常量,也沒有其他地方引用了這個字面量,如果這時發生內存回收,而且必要的話,這個“abc”常量就會被系統清理常量池。

判定一個類是否是“無用的類”需要滿足下面3個條件:

1、該類所有的實例都已經被回收,也就是java對中不存在該類的任何實例

2、加載該類的ClassLoader已經被回收

3、該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法

虛擬機可以對滿足上述條件的無用類進行回收

HotSpot虛擬機提供了-Xnoclassgc參數進行控制,還可以使用-verbose:class 以及 -XX+TraceClassLoading、-XX:+TraceClassUnLoading查看類加載和卸載信息,其中-verbose:class和-XXTraceClassLoading可以在product版的虛擬機中使用,-XX:+TraceClassUnLoading參數需要FastDebug版本虛擬機支持。

反射,動態代理 CGlib等Bycode 框架、動態生成jsp,OSGi這類頻繁自定義ClassLoader的場景都需要虛擬機具備類的卸載功能

3.3 垃圾收集算法

1、標記清除法? Mark-sweep算法? 器主要不足的有兩個:一個是效率問題,另一個是空間問題,標記清除后會產生大量的不連續的內存碎片

2、復制算法:為了解決效率問題,出現了一種稱為“復制”copying的收集算法,主要思路為:將容量分為大小相等的兩塊,每次只使用其中的一塊。,當這一塊的內存用完了,就將還存貨的對象復制到另外一塊上面,然后再把已經用過的內存空間一次性清理掉。該方法的缺點:大幅度縮減了內存的容量。

但該商業虛擬機都采用這種方法來回收新生代

研究表明新生代中的對象98%都是朝生夕死,所以不需要按照1:1的比例來劃分內存空間,而是將內存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,將Eden和Survivor中存活的對象一次性復制到另一塊Suvivor空間上,也就是每次新生代中可用內存空間為整個新生代容量的90%(80+10)只有10%的內存會被浪費,當Survivor空間不夠用的時候,需要依賴老年代進行分擔

3、標記-整理算法

標記存活的對象都向一段移動,然后直接清理掉端以外的內存,

4、分代收集算法

該算法根據對象生存周期的不同將內存分為幾塊。一般分為新生代和老年代,這樣就可以跟腱炎不聽年代的特點,采用最適當的收集算法,在新生代中,選用復制算法,在老年代中,存活率較高、沒有額外空間對其進行分擔,就必須使用標記清理或者標記整理算法進行回收。

3.4HotSpot的算法實現

以GC root節點找引用鏈可達性,體現在GC停頓上,確保快照的一致性,即分析時間整個執行系統看起來凍結在某一個時間點上(Sun將這一時間成為stop the world),即使在CMS收集器(CMS收集器是JAVA虛擬機中垃圾收集器的一種。它運行在JAVA虛擬機的老年代中。CMS收集器是基于“標記-清除”算法實現的),也必須停頓。

目前主流虛擬機使用準確是GC(exact VM)

在HotSpot的實現中,是使用一組稱為OopMap的數據結構來達到這個目的,在給數據結構的協助下,HotSpot可以快速且準確的完成GC roots枚舉,并且室友在安全點(safepoint)才幾率特定的位置信息。安全點設置不能太少以至于讓GC等待時間太長,

關于跑到安全點停頓下來,有兩種方案可供選擇:搶險式中斷(preemptive Suspension)和主動式(Voluntary Suspension)其中搶先式中斷不需要線程的執行代碼主動去配合,在GC發生時,首先把所有線程全部中斷,如果發現有線程中斷的地方不在安全點上,就恢復線程,讓它跑到安全點上。現在幾乎沒有搶先式中斷來暫停線程從而響應GC事件。

3.4.3安全區域:

safe region? 針對處于Sleep狀態或者Blocked狀態,安全區域是指代碼片段中,引用關系不會發生變化,在這個區域中的任意地方開始GC都是安全的,當線程執行到safe region中代碼時,首先表示自己進入safe region,這樣JVM要發起GC時,就不用管自己為Safe Region狀態的線程。

JITJITcompiler,just-in-time compiler)將字節碼轉換成機器碼

3.5垃圾回收器

如果兩個收集器之間存在連線,就說明他們可以搭配使用。虛擬機所處的區域測表示屬于新生代收集器還是老年代收集器。

3.5.1

Serial收集器:是虛擬機新生代的單線程收集器:特點:重要的是它進行垃圾回收時,必須展廳其他所有的工作線程,直到收集結束。

parallel收集器:

Concurrent Mark serial CMS乃至GC收集器的最前沿成果Garbage First(G1)收集器。

ParNew收集器:

是Serial收集器的多線程版本,除了使用多條線程進行垃圾收集之外,其余行為包括收集器可用的所有控制參數

-XX:SurvivorRadio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等、收集算法、

CMS作為老年代的收集器,卻無法與JDK1.4中已經存在的新生代收集器 Parallel Scavenge配合工作,所以新生代只能選擇Parnew或者Serial收集器中的一個。ParNew收集器也是使用-XX:+UseConcMarkSweepGC選項后的默認新生代收集器,也可以使用-XX:+UseParNewGC來進行制定。可以使用-XX:ParallelGCThreads參數來限制垃圾收集的線程數

并發concurrent 指用戶線程與來及收集線程同時執行,(但不一定是濱興,可能會交替執行),用戶城西繼續運行,而垃圾收集程序運行于另一個CPU上

并行parallel? 指多條垃圾搜集線程并行工作,但此時用戶線程任然處于等待狀態

Parallel Scavenge收集提供了兩個參數用于精確控制吞吐量

分別是控制最大兩級收集停頓時間的-XX:MaxGCPauseMillis參數

直接設置吞吐量大小的-XX:GCTimeRatio參數

MaxGCPauseMillis參數允許的值是一個大一0的毫秒數,收集器將盡可能地保證內存回收花費的時間不超過設定值。不過大家不要認為如果把這個參數的值設置得稍小一點就能是的系統的垃圾收集速度變得更快,GC停頓時間縮短是1??犧牲吞吐量和新生代空間來換取的:每次收集的的少,客戶停頓的時間少,但收集的頻次變高,吞吐量下降。

垃圾收集時間占總時間的比例,相當于是吞吐量的倒數。如果把參數設置為19,那么允許最大GC時間就占總時間的5%

(即1/(1+19)),默認值為99,就是允許最大1%(1/(1+99))的垃圾收集時間。

由于與吞吐量關系密切,Parallel Scavenge收集器也經常稱為“吞吐量優先”收集器。

除上述兩個參數之外,Parallel Scavenge 收集器還有一個參數-XX:UseAdaptiveSizePolicy值得關注。這是一個開關參數,當這個參數打開之后,就不需要手工指定新生代的大小(-Xmm)、Eden與Survivor區的比例(-XX:SurvivorRadio)\晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細節參數了,虛擬機會根據運行情況收集性能監控信息。

這種調節方式成為GC自適應的調節策略(GC Ergonomics)。

手工優化存在困難的時候沒使用Parallel Scavenge收集器配合自適應調節策略,把內存管理任何交給虛擬機去完成

只需要把基本的內存數據設置好:如

-Xmx設置最大堆

MaxGCPauseMillis參數最大停頓時間

GCTimeRadio吞吐量

自適應調節策略也是Parallel Scavenge收集器與ParNew收集器的一個重要區別

3.5.4 Serial Old 收集器

serial Old 是Serial收集器的老年代版本,單線程收集器,使用“標記-整理”算法。這個收集器的主要意義:

給Client模式下的虛擬機使用。

如果再Server模式下,有兩大用途:一種用途是在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用另一種用途是作為CMS收集器的后備預案,在并發收集發生ConCurrent mode failure時使用。

3.5.5parallel old 是Paralllel scavenge收集器的老年代版本,使用多線程和“標記和整理”算法

在parallel old收集器出現后,“吞吐量優先”收集器終于有了比較名副其實的應用組合,在注重吞吐量以及CPU資源敏感的場合,都可以優先考慮Parallel Scanvenge加Parallel Old收集器。

3.5.6 CMS收集器

CMS(concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。

ConCurrent Low Pause Collector

整個過程分為4個步驟:

1、初始標記 CMS initial mark 標記GC roots 速度快

2、并發標記 CMS concurrent mark? 耗時長 GC roots tracing的過程

3、重新標記 CMS remark? 動態過程中堆標記進行修正,該過程耗時稍比初始標記時間長一些

4、并發清楚 CMS concurrent sweep? 耗時長

其中1,3標記仍然需要stop the world

缺點:

CMS收集器對CPU資源非常敏感。-->Incremental ConCurrent Mark Sweep/ i-CMS 增量式并發收集器:搶占式來模擬多任務機制的思想一樣,就是在并發標記、清理的時候讓GC線程、用戶線程交替運行,盡量減少GC線程的獨占資源的時間,實踐證明,增量時的CMS收集器效果很一般,不提倡使用

CMS收集器無法處理浮動垃圾(floating Garbage),可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生。由于CMS并發清理階段用戶線程還在運行之后,CMS無法在檔次收集中處理掉它們,只要留待下一次GC時再清理掉。這一部分稱為“浮動垃圾。”隨著用戶線程的持續運行,伴隨的垃圾也會隨之增加,因此CMS收集器不能像其它收集器那樣等到老年代幾乎全部填滿再進行收集,需要預留當年代使用了68%的空間后就會被激活。這是一個保守的設置,如果再應用中老年代增長不是太快,可以適當提高參數-XX:CMSInitiatingOccupancyFraction的值來提高觸發百分比,以降低內存回收次數從而獲得更好的性能。

在JDK1.6中,CMS收集器的啟動閾值已經提升至92%。要是CMS運行期間預留的空間無法滿足程序需求,就會出現一次Concurrent Mode Failure 失敗,這是虛擬機啟動后備預案:臨時啟用Serial Old收集器來重新進行來年代垃圾收集。這樣停頓時間就很長。所以說參數-XX:CMSInitatingOccupancyFraction設置的太高導致大量的Concurrent Mode Failure失敗性能反而降低

CMS是基于“標記-清楚”算法實現的收集器,收集結束時會產生大量的空間碎片,這樣很容易導致老年代內存大部分為空間剩余,但是無法找到足夠大的連續空間來分配當前的對象,不得不提前出發一次Full GC辦法為了解決這個問題,CMS收集器提高了一個-XX:+UseCMSCompactAtFullCollection開關參數,用于在CMS收集器頂不住要進行FullGC時,開啟內存碎片的合并整理過程。代價就是該過程是無法并發,并且停頓時間會有點長(比FullGC端),虛擬機還提供了-XX:CMSFullGCsBeforeCompaction,這個參數是用于設置執行多少次不壓縮Full GC后,跟著來一次帶壓縮的(默認值為0,表示每次進入Full GC 時都進行隨便整理)

3.5.7G1? garbage first 收集器,最前沿的成果之一,早在1.7正式商業,是一款面向服務端應用的垃圾收集器。

具備如下特點:

并行和并發:G1能充分利用多CPU、多核環境下的硬件優勢,使用多個CPU來縮短stop-the-world停頓時間,部分其他收集器原本需要停頓線程實行的GC動作,G1收集器任然可以通過并發的方式讓java程序繼續執行。

分代收集:分代在G1中依然保留。G1可以不需要其他收集器配合就能獨立管理整個GC堆,但它能夠采用不同的方式去處理新創建的對象和已經存活了一段時間、熬過多次GC的舊對象以獲取更好的收集效果

空間整合:與CMS標記清理算法不同,G1從整體來看是基于標記整理算法實現的收集器,從兩個region之間上來看是基于復制算法實現的,這兩種算法意味著G1運行期間不會產生內存空間碎片,收集后能提供規整的可用內存。這種特性有利于程序長時間運行,分配大對象時不會因為無法找到連續空間而提前出發一次GC

可預測的停頓:這是G1相對于CMS的另一個大優勢,降低了停頓時間是G1和CMS共同的關注點,但是G1除了追求低停頓外,環能簡歷可預測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間片內,小號在垃圾收集上的時間不得超過N毫秒,這似乎詩經實時java(RTSJ)的垃圾收集器的特征了。

在G1之前,收集器收集的范圍都是新生代或者老年代

G1將整個Java堆劃分為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離,他們都是一部分Region。

G1收集器建立可預測的停頓時間模型,是因為它可以有計劃的避免在整個java堆中進行全區域的垃圾收集。G1跟蹤各個Region里面的垃圾堆積的價值大小(回收所獲得的空間大小以及所需要時間的經驗值),在后臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region,這也是Garbage-First名稱的由來。這種使用Region劃分內存空間以及有優先級的區域回收方式,保證了G1收集器在有限的時間內可以獲得盡可能高的收集效率。

為了避免采用可達性判斷時,對java堆的全局掃描,避免minor GC的效率降低。G1收集器中,Region之間的對象引用以及其他收集器中的新生代與老年代之間的對象引用,虛擬機都是使用Remembered Set 來避免全堆掃描。G1中每個Region都有一個與之對應的Remembered Set,虛擬機發現程序在對Reference類型的數據進行了寫操作時,會產生一個write Barrier暫時中斷寫操作。檢查Reference引用的對象是否處于不同Region之中,(分代的例子就是檢查是否老年代中的對象引用了新生代中的對象,如果是,便通過cardTable把相關引用信息記錄到被引用對象所屬的region的Remembered Set中),當進行內存回收時,在GC根節點的枚舉范圍中,加入Remembered Set 即可保證不對全堆掃描也不會遺漏。



為了避免采用可達性判斷時,對java堆的全局掃描,避免minor GC的效率降低。G1收集器中,Region之間的對象引用以及其他收集器中的新生代與老年代之間的對象引用,虛擬機都是使用Remembered Set 來避免全堆掃描。G1中每個Region都有一個與之對應的Remembered Set,虛擬機發現程序在對Reference類型的數據進行了寫操作時,會產生一個write Barrier暫時中斷寫操作。檢查Reference引用的對象是否處于不同Region之中,(分代的例子就是檢查是否老年代中的對象引用了新生代中的對象,如果是,便通過cardTable把相關引用信息記錄到被引用對象所屬的region的Remembered Set中),當進行內存回收時,在GC根節點的枚舉范圍中,加入Remembered Set 即可保證不對全堆掃描也不會遺漏。

注意:文中提到minor GC 知識GC中的一種,對此不理解的可以參考

http://www.importnew.com/15820.html

其中主要定義為:Minor GC對 Eden 和survivor區域進行回收

major GC 是清理老年代

Full GC 是清理整個堆空間

3.5.8理解GC 日志

是處理java虛擬機內存問題的基礎技能,

GC日志

GC full GC 說明這次垃圾收集的停頓類型,

如果有Full 說明這次GC 發生了stop-the-world

下面新生代收集器parNew的日志也會出現 [Full GC]

接下來的【DefNew? 【Tenured 【Perm表示GC發生的區域

DefNew? Defalt New Generation

ParNew? ? parallel New Generation

Parallel Scavenge 收集器 psYoungGen

后面括號內部的3324k->152K(3712k)含義就是GC前內存區域已使用量->GC后該內存區域使用量(該內存區域總容量)

再往后,‘0.0025925.secs’表示該內存區域GC所占用的時間 單位為秒

[Times:user=0.01 sys=0.00,real=0.02 secs]這里的user、sys、real與linux的time命令輸出的時間含義一致,分別代表用戶態消耗的cpu時間,內核態消耗的cpu事件和操作從開始到結束所經過的墻鐘時間(wall clock time)cpu時間和墻鐘時間的區別是,墻鐘時間表示費運算的等待耗時,列入磁盤IO,等待線程阻塞,而CPU時間不包括這些耗時。



3.6 內存分配與回收策略

java技術體系中所提倡的自動內存管理最終可以終結為自動化地解決了兩個問題:

1、給對象分配內存以及回收分配給對象的內存

2、對象分配內存

對象的內存分配,忘大方向講,就是在堆上分配(但也可能經過JIT compiler just-in-time)變異后被拆散為標量類型并間接地棧上分配,對象主要分配在新生代的Eden上,如果啟動了本地線程分配緩沖,將按線程優先在TLAB(threading local allocate buffer)上的分配。少數情況下可能會直接分配在老年代中,分配的規則并不是百分百的固定,其細節取決于當前使用的是哪一種垃圾收集器組合。還有虛擬機與內存相關的參數設置。

最普遍的內存分配規則:

換句話說:驗證的是在使用Serial/Serial Old收集器下

ParNew/Serial Old收集器組合的規則基本一致的內存分配和回收的策略

3.6.1 對象優先在Eden分配

大多數情況下,對象在新生代Eden區中分配。當Eden區沒有足夠的空間進行分配時,虛擬機將發起一次Minor GC

-XX:+PrintGCDetails? 這個收集器日志參數 并且在進程退出的時候輸出當前的內存各區域分配情況。

-Xms20M -Xmx20M -Xmn10M 這個三個參數限制了java堆大小為20M,其中10M分配給新生代,剩下的10M分配給老年代

-XX:SurvivorRatio=8 決定了新生代中Eden區與一個Survivor區的空間比例是8:1,

從結果也可以清晰地看到“eden space 8193k 、from space 1024k、to 1024k”的信息

這次minor GC結果為新生代6651k變為148k,而總內存占用量機會沒有減少(因為allocation1,allocation2,allocation3三個對象都是存活的,虛擬機幾乎沒有找到可回收的對象)。這次GC發生的原因是給allocation4分配內存的時候,發現Eden已經被占用6M,剩余空間已經不足以分配allocation4所需的4M內存,因此發生MinorGC。GC期間又發現虛擬機已經有的3個2MB對象全部無法放入Survivor空間(因為survivor只有1M大小,所以只好通過分配擔保機制提前轉移到老年代去)

Minor GC 和Full GC 的區別

1、新生代GC(Minor GC):指發生在新生代的垃圾收集動作,因為java對象大多數都具備朝生夕滅的特性,所以Minor GC扥長頻繁,一般回收速度也比較快。

2、老年代GC(MajorGC/Full GC):指發生在老年代的GC,出現了Major GC,經常會伴隨至少一次的Minor GC(但非絕對,在Parallel Scanvenge收集器的收集策略里就有直接進行Mahor GC的策略選擇過程)。Major GC的速度一般會比Minor GC 慢10倍以上

測試方式可以用


:jconsole? $pid



3.6.2 大對象直接進入老年代

所謂的大對象是指,需要大量連續內存空間的Java對象,最典型的大對象就是那種很長的字符串以及數組,大對象對虛擬機的內存分配來說就是一個壞消息(替java虛擬機抱怨一句,比遇到一個大對象更壞的消息是遇到一群朝生夕滅的短命大對象,寫程序的時候應當避免),經常楚翔大對象容易導致內存還有不少空間是就提前出發垃圾收集以獲取足夠連續的空間來安置他們

虛擬機提供了一個-XX:PretentureSizeThreshold參數,令大于這個設置值的對象直接在老年代分配。這樣做的目標是避免在Eden區及兩個Survivor區之間發生大量的內存復制,新生代采用復制算法收集內存。

PretentureSizeThreshold參數只對Serial和ParNew兩款收集器有效

3.6.3 長期存活的對象將進入老年代

為了表示那些對象放在新生代,那些對象放在老年代中,虛擬機給每個對象定義了一個對象年齡(Age)計數器。如果對象在Eden出生并經過第一次monor GC后任然存活,并且能被survivor容納的話,將被移到Survivor區中每“熬過”一次Minor GC,年齡就增加1歲,當它的年齡增加到一定程度(默認為15歲),將會被晉升到老年代中。對老年代的年齡閾值,可以通過參數-XX:MaxTenuringThreshold設置

嘗試:-XX:MaxTenuringThreshold=1 和-XX:MaxTenuringThreshold=15

3.6.4 動態對象年齡判定

如果再Survivor空間中相同年齡所有對象大小的的總和大于Survivor空間的一半,年齡大于或者等于該年齡的對象就可以直接進入老年代,無語等到MaxTenuringThreshold中的要求的年齡

3.6.5 空間分配擔保

在發生Minor GC之前,虛擬機會先檢查老年代最大可用的連續空間是否大于新生代所有對象總空間,如果這個條件成立,那么Minor GC 可以確保安全,如果不成立,則

,則虛擬機會查看HandlePromotionFailure設置值是否允許擔保失敗。如果允許,那么會繼續檢查老年代最大可用的連續空間是都大于歷次晉升到老年代對象的平均大小。

如果大于,將嘗試著進行一次Minor GC,盡量這次Minor-GC是有風險的;

如果小于,或者HandlePromotionFailire設置不允許毛線,那這時也要改為進行一次Full GC

當出現在Minor GC后任然有大量的對象存活的情況下,最極端的情況就是內存回收后新生代中所有對象都存活,就需要老年代進行分配分擔,把survivor無法容納的對象直接進入老年代。與生活中的貸款擔保類似,老年代要進行這樣的擔保,前提是老年代本身還有容納這些對象的剩余空間,一共有多少對象會活下來在實際完成內存回收之前是無法明確知道的。所以只好取之前每一次回收晉升到老年代對象容量的平均值作為經驗值,與老年代的剩余空間進行比較,決定是都進行Full GC讓老年代騰出更多的空間。

取平均值進行比較其實仍然是一種動態概率的手段,如果Minor GC 存活后的對象激增,遠遠高于平均值的話,依然會導致擔保失敗(Handle Promotion Failure)。如果出現了HandlePromotionFailure失敗,那就只好在失敗后重新發起一次Full GC。雖然擔保失敗時繞的圈子是最大的。但大部分情況下都還是會將HandlePromotionFailure開關打開,避免Full GC 過于頻繁

在JDK 1.6后,規則變為只要老年代的連續空間大于新生代對象總大小或者歷次晉升的平均大小就會進行Minor GC,否則進行Full FC

第四章:虛擬機性能監控與故障處理工具

4.1

4.2 JDK的命令行工具

-Dcom.sunmanagement.jmxremote 開啟JMX管理功能

在1.6的虛擬機上,Sun JDK監控和故障處理工具

名稱? 主要作用

jps JVM proces Status Tool 顯示指定系統內所有的HotSpot虛擬機進程

jstat JVM Statistics Monitoring Tool,用于手機HotSpot虛擬機各方面的運行數據

jinfo Configuration Info for Java 顯示虛擬機配置信息

jmap Memory Map for Java,生成虛擬機的內存轉儲快照(headdump 文件)

jhat JVM Heap Dump Browser 用戶分析headdump文件,它會簡歷一個HTTP、Html服務器,讓用戶可以在瀏覽器上查看分析結果

jstack Stack Trace For Java 顯示虛擬機的線程快照

4.3 JDK的可視化工具

兩個強大的可視化工具:JConsole和VisualVM

第5章 調優案例分析與實戰

方案一、對于用戶交互性強,對停頓時間敏感的系統,可以給java虛擬機分配超大堆的前提是有把我應用程序的Full GC頻率控制得足夠低,至少要低到不會影響用戶使用,譬如十幾小時乃至一天才出現一次Full GC

控制Full GC頻率的關鍵是看應用中絕大多數對象能夠符合“朝生夕滅”的原則。即多數對象的生存時間不應太長,尤其是不能有成批量的,長生存的時間的大對象產生,這樣才能保障老年代空間的穩定。

在大多數網站形式的應用里,主要對象的生存周期都應該是請求級或者頁面級的,會話級全局級的長生命對象相對很少。只要代碼寫得合理,應當都能實現在超大堆中正常使用而沒有頻繁的Full GC甚至沒有Full GC

缺點:1、內存回收導致的長時間停頓

2、現階段,64位JDK的性能測試結果普遍低于32位JDK

3、 需要保證程序足夠穩定,因為這種應用要是產生堆溢出幾乎就無法產生堆轉儲快照(因為產生十幾個G乃至更大的Dump文件)哪怕產生了快照也幾乎無法分析。

4、相同程序在64位JDK消耗的內存一般比32位JDK大,這是由于指針膨脹,以及數據類型對齊補白等因素導致

方案二、一臺物理機器上啟動多個應用服務器進程,每個服務器進程分配不同端口,然后再前段搭建一個負載均衡器,以反向代理的方式來分配訪問請求。

在一臺物理機器上簡歷邏輯集群的目的僅僅是為了盡可能利用硬件資源,斌不需要關心狀態保留、熱轉移之類的高可用性需求,也不需要保證每個虛擬機進程有絕對準確的負載均衡,因此使用無Session復制的親合式集群是一個相當不錯的選擇。我們僅僅需要保障集群具備親和性,也就是均衡按照一定的規則算法(一般根據SessionID分配)將一個固定的用戶請求永遠分配到固定的一個集群節點進行處理即可,這樣程序開發階段的基本不用為集群環境做特別的考慮了。

缺點:

1、盡量避免節點競爭全局資源、最典型的就是磁盤競爭,各個節點如果同時訪問某個磁盤文件(尤其是鬢發操作榮出現問題),很容易導致I/O異常

2、很難最高效地利用某些資源池,譬如鏈接池子,一般都是在各個節點建立自己獨立的連接池,這樣有可能導致一些節點池滿了而另一些節點仍有較多空余。盡管空余使用集中式JNDI,但這個有一定復雜性并且可能帶來額外的性能開銷

3、各個節點任然不可避免受到32位內存限制,win32 2G內存,linux/unix 4G限制

4、大量使用本地緩存(如大量使用K/V緩存)應用,在邏輯集群中會造成較大的內訓浪費,因為每個邏輯節點都有一份緩存,這個時候可以考慮把本地緩存改為集中式緩存。

最終方案:

部署方案是:建立5個32位JDK的邏輯集群,每個進程按2G內存計算(其中堆固定為1.5G),占用了10G內存,另外建立一個Apache服務作為前端代理訪問門戶。考慮到用戶對影響速度比較關心。并且文檔服務的主要壓力集中在磁盤和內存訪問,CPU資源敏感度較低,因此改為CMS收集器進行垃圾回收部署方式調整后。效果很好

5.2.2? 集群間同步導致的內存溢出

5.3.2 堆外內存導致的溢出錯誤

系統使用逆向AJAX技術(也稱為Comet或者Server Side Push),選用CometD 1.1.1 作為服務器端推送框架

直接內存不能像新生代或者老年代那樣,發現空間不足就通知收集器進行垃圾回收,它只能等待老年代滿了之后Full GC

大量的NIO操作需要使用到Direct Memory

1、Direct Memory:可以通過-XX:MaxDirectMemorySize調整大小內存不足拋出outOfMemoryError或者

2、線程堆棧:可通過-Xss調整大小,內存不足時拋出StackOverFlowError(縱向無法分配,即無法分配新的棧幀)

3、Socket緩存區:每個Socket連接都Reseive和Send兩個緩存區,分別占大約37KB和25KB內存,連接多的話這塊內存占用也比較客觀。如果無法分配,則可能拋出IOEXCEPTION:too many open files

4、JNI代碼,如果代碼中使用JNI調用本地庫,那本地庫的內存也不在堆中。

5、虛擬機核GC:虛擬機、GC的代碼執行也需要一定的內存

5.2.4 外部命令導致系統放慢

一個數字校園的應用系統

中間件為GlassFish

壓力測試,發現求情響應時間比較慢,通過操作系統的mpstat工具發現CPU使用率很高,

找到答案:每個用戶請求的處理都需要執行一個外部的Shell腳本來獲得系統的一些信息。執行這個shell腳本是通過java的Runtime.getRuntime().exec()方法來調用的。

這種方式在jaba虛擬機中是非常消耗資源的操作,即使外部命令本身能很快執行完畢,頻繁調用時穿件進程的開銷非常可觀。虛擬機執行這個命令的過程是:首先克隆一個和當前虛擬機擁有一樣環境變量的進程,再用這個新的進程去執行外部命令,最后再退出這個進程,如果頻繁執行這個操作,系統的消耗會很大,

5.2.5 服務器JVM進程奔潰

解決方法:通知OA門戶方修復無法使用的集成接口,并將一部調用改為生產者/消費者模式的消息隊列實現后,系統恢復正常

5.2.6 不恰當數據結構導致內存占用過大

內存配置為 -Xms4g -Xmx8g -Xmnlg,使用ParNew+CMS的收集器組合,平時堆外服務的Minor GC 30ms,可以接受 ,但業務上需要每10分鐘加載一個80M的數據文件到內存進行數據分析,這些數據形成內純種超過100萬個HashMap Entry 在這段時間里面Minor GC就會造成500

毫秒停頓,對于這個停頓時間接受不了。

這個案例:發現平時的Minor GC時間很短,原因是新生代大部分對象都是可清楚的,在Minor GC之后Eden和Survivor基本上處于完全空閑的狀態。而在分析數據文件期間,800MB的Eden空間很快就被填滿引發GC,但Minor GC之后,新生代中絕大部分對象依然是存活的。,我們知道ParNew收集器是使用的是復制法,這個算法的搞笑事簡歷在大部分對象都朝生夕滅的特性上,如果存活對象過多把這些對象復制到Survivor并維持這些對象的引用的正確稱為一種承重的負擔,因此導致GC暫停時間明顯變長。如果不修改程序僅從GC調優角度去解決這個問題,可以考慮將Survivor空間去掉(加入參數 -XX:SurvivorRatio=65536、-XX:MaxTenuringThreshold=0或者-XX:+AlwaysTenure),讓新聲帶中存活的對象在第一次Minor GC后立即進入老年代,等到Major GC的時候再清理它們

第五部分? 高效的并發

第12章? java內存模型與線程

衡量一個服務型能的高低好壞,每秒事務處理數(Transactions Per Second TPS)是重要的,它代表一秒服務器端響應的請求總數,而tps最重要的指標之一,它代表一秒內服務端平均能響應的請求總數,程序線程并發協調有條不紊,效率自然就會越高;

物理計算機中的并發問題,物理機遇到的并發問題與虛擬機中的情況有不少相似之處,物理機對并發的處理方案對于虛擬機的實現也有相當大的參考意義。

12.3.5 原子性,可見性和有序性

介紹完java內存模型的相關操作和原則,回顧下java模型的特征。java內存模型是圍繞著在并發過程中如何處理原子性,可見性和有序性三個特征來建立,

1、原子性(Atomicity):由java內存模型來直接保證的原子性變量操作包括read,load、assign、use,store和write,我們大致可以認為基本數據類型的訪問讀寫是具備原子性的,例外是long和double的飛原子性協定。滿足更大范圍的原子性保證,java內存模型還提供了lock和unlock操作來滿足這種需求,synchronized

2、可見性 (Visibility):java內存模型是通過在變量修改后將新值同步回住內存,在變量讀取前從主內存刷新變量值這種依賴主內存作為傳遞媒介的方式來實現可見性,無論是普通變量還是volatile變量都是如此,普通變量與volatile變量的區別是,volatile的特殊規則保證了新值能理解同步主內存,以及每次使用前立刻從主內存刷新。因此可以說volatile保證了多線程操作時變量的可見性

3、有序性(Ordering):Synchronized? 表示“同一個變量在同一個時刻只允許一條線程對其進行lock操作”

12.3.6先行發生原則

先行發生原則(happens-before)的原則,這個原則非常重要,它是判斷數據是否在競爭、線程是都安全的重要依據,依靠這個原則,我們可以通過幾條規則一攬子地解決并發環境下兩個操作之間是否可能存在沖突的所有問題。

具體:先行發生時java內存模型中定義的兩項操作之間的偏序關系,如果說操作A先行發生于操作B,其實就是說在發生操作B之前,操作A產生的影響能被操作B觀察到,“影響”包括修改了內存中共享變量的值、發送了消息、調用了方法等

12.4 Java與線程

12.4.1 線程的實現

實現線程主要有3中方式:使用內核線程實現、使用用戶線程實現和使用用戶線程加輕量級進程混合實現。

1、內核線程實現:Kernel-Level Thread KLT)就是直接有操作系統內核(Kernel 下稱內核)支持的線程。

2、用戶線程實現:一個線程只要不是內核線程,就可以認為是用戶線程(Used Thread UT)

12.4.2 java線程調度

線程調度室指系統為線程分配處理器的使用權的過程,主要調度方式有兩種,分別是協同式線程調度(cooperative threading scheduling)和搶占式線程調度 Preemptive Threads scheduling,

如果使用協同式調度的多線程系統,線程的執行時間由線程本身來控制,線程把自己的工作執行完之后,要主動通知系統切換到另外一個線程。協同式多線程最大的好處是現實簡單,而且線程要把自己的事情干完才會進行線程切換,切換操作的最大好處就是現實簡單,而且由于線程要把自己的事情干完才會進行線程切換,切換操作對線程自己是可知的,所以沒有什么線程同步問題。缺點:線程執行時間不可控制;

如果是搶占式調度的多線程系統,那么每個線程由系統來分配執行時間,線程的切換不由線程本身決定(在java中,Thread。yeild())可以讓出執行時間,但是要獲取執行的時間的話,線程本身是沒有辦法的。在這種實現的線程調度方式下,線程的執行是時間是系統可控的,也不會有一個線程導致進程的堵塞問題。

java使用的線程調度方式是搶占式調度。

第13章 線程安全與鎖優化

1、互斥同步,面臨線程阻塞和喚醒所帶來的性能問題,這種稱為阻塞同步(blocking Synchronization),這是一種悲觀的并發策略,悲觀主要體現在只要不去做正確的同步措施(如加鎖),那就肯定會出現問題,無論數據是否真的出現競爭,它都要進行加鎖,典型的synchronized? ReetrantLock? 綁定多條件

2、非阻塞同步:基于沖突檢測的樂觀并發策略,通俗說,就是先進行操作,如果沒有其他線程爭用共享數據,那操作就成功了。如果共享數據有爭用,產生了沖突,那就再采取其他的補償措施(最常見的補償措施就是不斷的重試,知道成功為止),這種樂觀的鬢發策略的許多實現都不需要把線程掛起,因此這種同步叫做非阻塞同步(non-blocking Synchronization)

通過以下方式實現:

1、測試設置(Test-and-Set)

2、獲取并增加(Fetch-and-Increment)

3、交換(swap)

4、比較并交換(Compare-and-Swap,下文稱CAS)。

5、加載鏈接/條件存儲(load-linked/store-conditional? LL/SC)

13.3鎖優化

適應性自旋(Adaptive Spinning)

鎖消除(Lock Eliminination)

鎖粗化Lock Coarsening

輕量級鎖(LightWeight Locking)

偏向鎖 (Biased Locking)等

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容

  • 原文閱讀 前言 這段時間懈怠了,罪過! 最近看到有同事也開始用上了微信公眾號寫博客了,挺好的~給他們點贊,這博客我...
    碼農戲碼閱讀 6,003評論 2 31
  • 這篇文章是我之前翻閱了不少的書籍以及從網絡上收集的一些資料的整理,因此不免有一些不準確的地方,同時不同JDK版本的...
    高廣超閱讀 15,650評論 3 83
  • JVM架構 當一個程序啟動之前,它的class會被類裝載器裝入方法區(Permanent區),執行引擎讀取方法區的...
    cocohaifang閱讀 1,684評論 0 7
  • 作者:一字馬胡 轉載標志 【2017-11-12】 更新日志 日期更新內容備注 2017-11-12新建文章初版 ...
    beneke閱讀 2,222評論 0 7
  • 《羋月傳》第70集中的霸氣演講和各種職場版本給刷屏了!很多人認為是老板給員工熬雞湯、甚至是傳銷組織洗腦推銷的經典案...
    鹿偉倫閱讀 1,713評論 1 1