一:虛擬機內(nèi)存圖解 JAVA程序運行與虛擬機之上,運行時需要內(nèi)存空間。虛擬機執(zhí)行JAVA程序的過程中會把它管理的內(nèi)存劃分為不同的數(shù)據(jù)區(qū)域方便管理。 虛擬機管理內(nèi)存數(shù)據(jù)區(qū)域劃分如下圖: 數(shù)據(jù)區(qū)域分類: 方法區(qū): (Method Area) 虛擬機棧 : (VM Stack) 本地方法棧 : (Native Method Stack) 堆: (Heap) 程序計數(shù)器: (Program Counter Register) 直接內(nèi)存 : (Direct Memory) 說明: 1. 程序計數(shù)器 行號指示器,字節(jié)碼指令的分支、循環(huán)、跳轉、異常處理、線程恢復(CPU切換),每條線程都需要一個獨立的計數(shù)器,線程私有內(nèi)存互不影響,該區(qū)域不會發(fā)生內(nèi)存溢出異常。 2. 虛擬機棧 是線程私有的,聲明周期與線程相同,虛擬機棧是Java方法執(zhí)行的內(nèi)存模型,每個方法被執(zhí)行時都會創(chuàng)建一個棧幀,即方法運行期間的基礎數(shù)據(jù)結構,棧幀用于存儲:局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等,每個方法執(zhí)行中都對應虛擬機棧幀從入棧到處棧的過程。 是一種數(shù)據(jù)結構,是虛擬機中的局部變量表,對應物理層之上的程序數(shù)據(jù)模型。 局部變量表,是一種程序運行數(shù)據(jù)模型,存放了編譯期可知的各種數(shù)據(jù)類型例如: Boolean、byte、char、short、int、float、long、double、對象引用類型(對象內(nèi)存地址變量,指針或句柄),程序運行時,根據(jù)局部變量表分配棧幀空間大小,在運行中,大小是不變的異常類型:stackOverFlowError 線程請求棧深度大于虛擬機允許深度 OutOfMemory 內(nèi)存空間耗盡無法進行擴展。 3. 本地方法棧 與虛擬機棧類似,虛擬機棧為Java程序服務,本地方法棧支持虛擬機的運行服務,具體實現(xiàn)由虛擬機廠商決定,也會拋出 stackOverFlowError、OutOfMemory異常。 4. 堆 是虛擬機管理內(nèi)存中最大的一部分,被所有線程共享,用于存放對象實例(對象、數(shù)組),物理上不連續(xù)的內(nèi)存空間,由于GC收集器,分代收集,所以劃分為:新生代 Eden、From SurVivor空間、To SurVivor空間,allot buffer(分配空間),可能會劃分出多個線程私有的緩沖區(qū),老年代。 5. 方法區(qū) 與堆一樣屬于線程共享的內(nèi)存區(qū)域,用于存儲虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼(動態(tài)加載OSGI)等數(shù)據(jù)。理論上屬于java虛擬機的一部分,為了區(qū)分開來叫做 Non-Heap非堆。 這個區(qū)域可以選擇不進行垃圾回收,該區(qū)域回收目的主要是常量池的回收,及類型的卸載class,內(nèi)存區(qū)不足時會拋出OutOfMemory異常 運行時常量池: 方法區(qū)的一部分,Class的版本、字段、接口、方法等,及編譯期生成的各種字面量、符號引用,編譯類加載后存放在該區(qū)域。會拋出OutOfMemory異常。 6. 直接內(nèi)存 直接內(nèi)存不屬于虛擬內(nèi)存區(qū)域,是一種基于通道與緩沖區(qū)的IO方式,可以使用本地函數(shù)直接分配堆外內(nèi)存,在堆中存儲引用的外部內(nèi)存地址,通過引用完成對直接引用內(nèi)存的操作,1.4之后提供的NIO顯著提高效率,避免了堆內(nèi)存與Native內(nèi)存的來回復制操作,不受虛擬機內(nèi)存控制,會拋出OUtOfMemory異常。 二:對象訪問內(nèi)部實現(xiàn)過程 對象訪問 涉及到對象的地址變更狀態(tài)變更,內(nèi)存地址移動,變量、接口、實現(xiàn)類、方法、父類型等。 一、 句柄方式 (訪問) 二、指針方式 (訪問) 優(yōu)缺點: 句柄訪問方式:reference中存儲的是穩(wěn)定的地址,對象變更時只會改變句柄實例數(shù)據(jù)指針,引用本身不需要修改 指針訪問方式:優(yōu)點速度快,節(jié)省了指針定位時間開銷 三:內(nèi)存區(qū)域控制參數(shù)及對應溢出異常 開發(fā)過程中,或程序運行過程中每次遇到OutOfMemory異常或GC異?;騍tackOverflowError異常我們都是一堆參數(shù)亂配,都把值調(diào)大,只是大體知道是跟jvm內(nèi)存分配有關,具體應該怎么調(diào),對應的異常應該調(diào)整那些參數(shù),或者換句話說,jvm內(nèi)存分配區(qū)域中都分別對應那些參數(shù)大多數(shù)情況下都是不知道的,只是把相關的參數(shù)跳上去,預期結果都是應該起作用,到底能不能起作用,自己心里也沒底。下面就來說一下jvm堆、棧、方法區(qū)等內(nèi)存區(qū)域對應的參數(shù),及每個區(qū)域可能拋出的異常類型,發(fā)生異常的場景分析。 一、參數(shù)類型 1.堆空間參數(shù) 2.棧空間參數(shù) 3.方法區(qū)空間參數(shù) 4.本機直接內(nèi)存參數(shù) 二、異常類型 1.OutOfMemory異常 2.StackOverflowError異常 三、輔助參數(shù)說明 1.-XX: HeapDumpOnOutOfMemoryError 打印堆內(nèi)存異常時打印出快照信息 2.-XX: HeapDumpPath 快照輸出路徑 3.-Xmn指定eden區(qū)的大小 -XX:SurvirorRation來調(diào)整幸存區(qū)的大小 4.-XX:PretenureSizeThreshold設置進入老年代的閥值 四、參數(shù)說明、對應場景的異常 1.堆內(nèi)存參數(shù) -Xms:堆最小值(新生代和老年代之和) -Xmx:堆最大值(新生代和老年代之和) 當最小值=最大值時,這時堆內(nèi)存是不可擴展的。 例:-Xms80M -Xmx80M 通常將-Xmx和-Xms設置為一樣的大小來減少gc的次數(shù),堆內(nèi)存不足時拋出OutOfMemoryError異常。 2.棧內(nèi)存參數(shù) -Xss 例:-Xss128k 單線程下無論棧幀太大還是棧容量太小,及引用深度超過虛擬機允許深度都會拋出StackOverflowError每個方法壓入棧的幀大小是不一致的。多線程下當每個線程分配棧幀太大內(nèi)存不能夠擴展時拋出OutOfMemoryError異常線程棧幀越大,可創(chuàng)建的線程越少。 3.方法區(qū)參數(shù) -XX:PermSize方法區(qū)內(nèi)存最小值 -XX:MaxPermSize 方法區(qū)內(nèi)存最大值 各個線程共享的內(nèi)存區(qū)域,主要用來存儲類的元數(shù)據(jù)、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù) 例:-XX:PermSize=20M -XX:MaxPermSize=20M 異常類型 OutOfMemoryError : 原因:常量過多,或代理反射等使用頻繁 4.本機直接內(nèi)存參數(shù) -XX:MaxDirectMemorySize 例:-XX:MaxDirectMemorySize=10M 不足時拋出OutOfMemory異常 四:垃圾收集算法 經(jīng)典的垃圾回收算法以下幾種 一、標記--清除算法(Mark-Sweep) 回收前狀態(tài): 回收后狀態(tài): 優(yōu)缺點: 算法執(zhí)行分為兩個階段標記與清除,所有的回收算法,基本都 基于標記回收算法做了深度優(yōu)化 缺點:效率問題,內(nèi)存空間碎片(不連續(xù)的空間) 二、復制算法(Copying) 回收前狀態(tài): Eden內(nèi)存空間 8 Survivor1空間(From空間)1 Survivor2空間(To空間) 1 Eden內(nèi)存空間與Survivor空間 8:1 回收后狀態(tài): Survivor1空間(From空間)1 Eden內(nèi)存空間與Survivor空間 8:1 優(yōu)缺點: 比較標記清除算法,避免了回收造成的內(nèi)存碎片問題, 缺點:以局部的內(nèi)存空間犧牲為代價,不過空間的浪費比較小,默認8:1的比例1是浪費的。 復制也有一定的效率與空間成本 三、標記整理算法(Mark-Compact) 回收前狀態(tài): 回收后狀態(tài): 優(yōu)缺點: 避免了,空間的浪費,與內(nèi)存碎片問題。 缺點:整理時復制有效率成本。 五:垃圾收集器 一、七種垃圾收集器 (1) Serial(串行GC)-XX: UseSerialGC (2) ParNew(并行GC)-XX: UseParNewGC (3) Parallel Scavenge(并行回收GC) (4) Serial Old(MSC)(串行GC)-XX: UseSerialGC (5) CMS(并發(fā)GC)-XX: UseConcMarkSweepGC (6) Parallel Old(并行GC)-XX: UseParallelOldGC (7) G1(JDK1.7update14才可以正式商用) 二.1~3用于年輕代垃圾回收:年輕代的垃圾回收稱為minor GC 三.4~6用于年老代垃圾回收(當然也可以用于方法區(qū)的回收):年老代的垃圾回收稱為full GC G1獨立完成'分代垃圾回收' 注意:并行與并發(fā) 并行:多條垃圾回收線程同時操作 并發(fā):垃圾回收線程與用戶線程一起操作 四、常用五種組合 Serial/Serial Old ParNew/Serial Old:與上邊相比,只是比年輕代多了多線程垃圾回收而已 ParNew/CMS:當下比較高效的組合 Parallel Scavenge/Parallel Old:自動管理的組合 G1:最先進的收集器,但是需要JDK1.7update14以上 五. Serial/Serial Old 年輕代Serial收集器采用單個GC線程實現(xiàn)'復制'算法(包括掃描、復制) 年老代Serial Old收集器采用單個GC線程實現(xiàn)'標記-整理'算法 Serial與Serial Old都會暫停所有用戶線程(即STW) 說明: STW(stop the world):編譯代碼時為每一個方法注入safepoint(方法中循環(huán)結束的點、方法執(zhí)行結束的點),在暫停應用時,需要等待所有的用戶線程進入safepoint,之后暫停所有線程,然后進行垃圾回收。 適用場合: CPU核數(shù)<2,物理內(nèi)存<2G的機器(簡單來講,單CPU,新生代空間較小且對STW時間要求不高的情況下使用) -XX:UseSerialGC:強制使用該GC組合 -XX:PrintGCApplicationStoppedTime:查看STW時間 六.ParNew/Serial Old: ParNew除了采用多GC線程來實現(xiàn)復制算法以外,其他都與Serial一樣,但是此組合中的Serial Old又是一個單GC線程,所以該組合是一個比較尷尬的組合,在單CPU情況下沒有Serial/Serial Old速度快(因為ParNew多線程需要切換),在多CPU情況下又沒有之后的三種組合快(因為Serial Old是單GC線程),所以使用其實不多。 -XX:ParallelGCThreads:指定ParNew GC線程的數(shù)量,默認與CPU核數(shù)相同,該參數(shù)在于CMS GC組合時,也可能會用到 七.Parallel Scavenge/Parallel Old: 特點: 年輕代Parallel Scavenge收集器采用多個GC線程實現(xiàn)'復制'算法(包括掃描、復制)年老代Parallel Old收集器采用多個GC線程實現(xiàn)'標記-整理'算ParallelScavenge與Parallel Old都會暫停所有用戶線程(即STW) 說明: 吞吐量:CPU運行代碼時間/(CPU運行代碼時間 GC時間)CMS主要注重STW的縮短(該時間越短,用戶體驗越好,所以主要用于處理很多的交互任務的情況)Parallel Scavenge/Parallel Old主要注重吞吐量(吞吐量越大,說明CPU利用率越高,所以主要用于處理很多的CPU計算任務而用戶交互任務較少的情況) 參數(shù)設置: -XX: UseParallelOldGC:使用該GC組合 -XX:GCTimeRatio:直接設置吞吐量大小,假設設為19,則允許的最大GC時間占總時間的1/(1 19),默認值為99,即1/(1 99) -XX:MaxGCPauseMillis:最大GC停頓時間,該參數(shù)并非越小越好 -XX: UseAdaptiveSizePolicy:開啟該參數(shù),-Xmn/-XX:SurvivorRatio/-XX:PretenureSizeThreshold這些參數(shù)就不起作用了,虛擬機會自動收集監(jiān)控信息,動態(tài)調(diào)整這些參數(shù)以提供最合適的的停頓時間或者最大的吞吐量(GC自適應調(diào)節(jié)策略),而我們需要設置的就是-Xmx,-XX: UseParallelOldGC或-XX:GCTimeRatio兩個參數(shù)就好(當然-Xms也指定上與-Xmx相同就好) 注意: -XX:GCTimeRatio和-XX:MaxGCPauseMillis設置一個就好 不開啟-XX: UseAdaptiveSizePolicy,-Xmn/-XX:SurvivorRatio/-XX:PretenureSizeThreshold這些參數(shù)依舊可以配置,以resin服務器為例 <jvm-arg>-Xms2048m</jvm-arg> <jvm-arg>-Xmx2048m</jvm-arg> <jvm-arg>-Xmn512m</jvm-arg> <jvm-arg>-Xss1m</jvm-arg> <jvm-arg>-XX:PermSize=256M</jvm-arg> <jvm-arg>-XX:MaxPermSize=256M</jvm-arg> <jvm-arg>-XX:SurvivorRatio=8</jvm-arg> <jvm-arg>-XX:MaxTenuringThreshold=15</jvm-arg> <jvm-arg>-XX: UseParallelOldGC</jvm-arg> <jvm-arg>-XX:GCTimeRatio=19</jvm-arg> <jvm-arg>-XX: PrintGCDetails</jvm-arg> <jvm-arg>-XX: PrintGCTimeStamps</jvm-arg> View Code 適用場合: 很多的CPU計算任務而用戶交互任務較少的情況不想自己去過多的關注GC參數(shù),想讓虛擬機自己進行調(diào)優(yōu)工作 八、調(diào)優(yōu)方法 8.1 新對象預留新生代 由于fullGC(老年代)的成本遠比minorGC(新生代和老年代)的成本大,所以給應用分配一個合理的新生代空間,盡量將對象分配到新生代減小fullGC的頻率 8.2 大對象進入老年代 將大對象直接分配到老年代,保持新生代對象的結構的完整性,以提高GC效率, 以通過-XX:PretenureSizeThreshold設置進入老年代的閥值 8.3 穩(wěn)定與震蕩的堆大小 穩(wěn)定的對大小是對垃圾回收有利的,方法將-Xms和-Xmx的大小一致 8.4 吞吐量優(yōu)先 盡可能減少系統(tǒng)執(zhí)行垃圾回收的總時間,故采用并行垃圾回收器 -XX: UseParallelGC或使用-XX: UseParallelOldGC 8.5 降低停頓 使用CMS回收器,同時減少fullGC的次數(shù) 九、獲取gc信息的方法 9.1 -verbose:gc或者-XX: PrintGC 獲取gc信息 9.2 -XX: PrintGCDetails 獲取更加詳細的gc信息 9.3 -XX: PrintGCTimeStamps 獲取GC的頻率和間隔 9.4 -XX: PrintHeapAtGC 獲取堆的使用情況 9.5 -Xloggc:D:\gc.log 指定日志情況的保存路徑 十、jvm調(diào)優(yōu)實戰(zhàn)-tomcat啟動加速 在tomcat的bin/catalina.bat文件的開頭添加相關的配置 六:監(jiān)控工具 監(jiān)控工具:一般問題定位,性能調(diào)優(yōu)都會使用到。 (一)、jps Jps是參照Unix系統(tǒng)的取名規(guī)則命名的,而他的功能和ps的功能類似,可以列舉正在運行的餓虛擬機進程并顯示虛擬機執(zhí)行的主類以及這些進程的唯一ID(LVMID,對應本機來說和PID相同),他的用法如下: Jps [option] [hostid] jps -q 只輸出LVMID jps -m 輸出JVM啟動時傳給主類的方法 jps -l 輸出主類的全名,如果是Jar則輸出jar的路徑 jps -v 輸出JVM的啟動參數(shù) (二)、jstat jstat主要用于監(jiān)控虛擬機的各種運行狀態(tài)信息,如類的裝載、內(nèi)存、垃圾回收、JIT編譯器等,在沒有GUI的服務器上,這款工具是首選的一款監(jiān)控工具。其用法如下: jstat [option vmid [interval [s|ms] [vount] ] ] jstat 監(jiān)控內(nèi)容 線程好 刷新時間間隔 次數(shù) jstat –gc 20445 1 20 :監(jiān)視Java堆,包含eden、2個survivor區(qū)、old區(qū)和永久帶區(qū)域的容量、已用空間、GC時間合計等信息 jstat –gcutil 20445 1 20:監(jiān)視內(nèi)容與-gc相同,但輸出主要關注已使用空間占總空間的百分比 jstat –class 20445 1 20:監(jiān)視類的裝載、卸載數(shù)量以及類的裝載總空間和耗費時間等 .......-gccapcity......:監(jiān)視內(nèi)容與-gc相同,但輸出主要關注Java區(qū)域用到的最大和最小空間 .......-gccause........:與-gcutil輸出信息相同,額外輸出導致上次GC產(chǎn)生的原因 .......-gcnew..........:監(jiān)控新生代的GC情況 .......-gcnewcapacity..:與-gcnew監(jiān)控信息相同,輸出主要關注使用到的最大和最小空間 .......-gcold..........:監(jiān)控老生代的GC情況 .......-gcoldcapacity..:與-gcold監(jiān)控信息相同,輸出主要關注使用到的最大和最小空間 .......-gcpermcapacity.:輸出永久帶用到的最大和最小空間 .......-compiler.......:輸出JIT編譯器編譯過的方法、耗時信息 .......-printcompilation:輸出已經(jīng)被JIT編譯的方法 (三)、jinfo jinfo的作用是實時查看虛擬機的各項參數(shù)信息jps –v可以查看虛擬機在啟動時被顯式指定的參數(shù)信息,但是如果你想知道默認的一些參數(shù)信息呢?除了去查詢對應的資料以外,jinfo就顯得很重要了。jinfo的用法如下: Jinfo [option] pid (四)、jmap map用于生成堆快照(heapdump)。當然我們有很多方法可以取到對應的dump信息,如我們通過JVM啟動時加入啟動參數(shù) –XX:HeapDumpOnOutOfMemoryError參數(shù),可以讓JVM在出現(xiàn)內(nèi)存溢出錯誤的時候自動生成dump文件,亦可以通過-XX:HeapDumpOnCtrlBreak參數(shù),在運行時使用ctrl break按鍵生成dump文件,當然我們也可以使用kill -3 pid的方式去恐嚇JVM生成dump文件。Jmap的作用不僅僅是為了獲取dump文件,還可以用于查詢finalize執(zhí)行隊列、Java堆和永久帶的詳細信息,如空間使用率、垃圾回收器等。其運行格式如下: Jmap [option] vmip 監(jiān)控堆棧信息主要用來定位問題的原因,生成堆??煺?/p> .......-dump......:生成對應的dump信息,用法為-dump:[live,]format=b,file={fileName} .......-finalizerinfo......:顯示在F-Queue中等待的Finalizer方法的對象(只在linux下生效) .......-heap......:顯示堆的詳細信息、垃圾回收器信息、參數(shù)配置、分代詳情等 .......-histo......:顯示堆棧中的對象的統(tǒng)計信息,包含類、實例數(shù)量和合計容量 .......-permstat......:以ClassLoder為統(tǒng)計口徑顯示永久帶的內(nèi)存狀態(tài) .......-F......:虛擬機對-dump無響應時可使用這個選項強制生成dump快照 例子:jmap -dump:format=b,file=yhj.dump 20445 (五)、jstack Jstack用于JVM當前時刻的線程快照,又稱threaddump文件,它是JVM當前每一條線程正在執(zhí)行的堆棧信息的集合。生成線程快照的主要目的是為了定位線程出現(xiàn)長時間停頓的原因,如線程死鎖、死循環(huán)、請求外部時長過長導致線程停頓的原因。通過jstack我們就可以知道哪些進程在后臺做些什么?在等待什么資源等!其運行格式如下: Jstack [option] vmid -F 當正常輸出的請求不響應時強制輸出線程堆棧 -l 除堆棧信息外,顯示關于鎖的附加信息 -m 顯示native方法的堆棧信息 (六)、jconsole 在JDK的bin目錄下,監(jiān)控內(nèi)存,thread,堆棧等 (七)、jprofile 類似于jconsole,比jconsole監(jiān)控信息更全面,內(nèi)存,線程,包,cup 類,堆棧,等等 |
|