1、?簡述JVM垃圾回收算法分類
常用的垃圾收集算法
JVM的內存結構包括五大區(qū)域:程序計數(shù)器、虛擬機棧彪笼、本地方法棧甩苛、堆區(qū)船庇、方法區(qū)。其中程序計數(shù)器、虛擬機棧、本地方法棧3個區(qū)域隨線程而生寸齐、隨線程而滅,因此這幾個區(qū)域的內存分配和回收都具備確定性抄谐,就不需要過多考慮回收的問題渺鹦,因為方法結束或者線程結束時,內存自然就跟隨著回收了蛹含。而Java堆區(qū)和方法區(qū)則不一樣毅厚、不一樣!(怎么不一樣說的朗朗上口),這部分內存的分配和回收是動態(tài)的浦箱,正是垃圾收集器所需關注的部分
復制算法:
為了解決Mark-Sweep算法的缺陷吸耿,Copying算法就被提了出來祠锣。它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊咽安。當這一塊的內存用完了伴网,就將還存活著的對象復制到另外一塊上面,然后再把已使用的內存空間一次清理掉板乙,這樣一來就不容易出現(xiàn)內存碎片的問題是偷。具體過程如下圖所示:
這種算法雖然實現(xiàn)簡單,運行高效且不容易產(chǎn)生內存碎片募逞,但是卻對內存空間的使用做出了高昂的代價,因為能夠使用的內存縮減到原來的一半馋评。
很顯然放接,Copying算法的效率跟存活對象的數(shù)目多少有很大的關系,如果存活對象很多留特,那么Copying算法的效率將會大大降低纠脾。
復制算法的提出是為了克服句柄的開銷和解決內存碎片的問題。它開始時把堆分成 一個對象 面和多個空閑面蜕青, 程序從對象面為對象分配空間苟蹈,當對象滿了,基于copying算法的垃圾 收集就從根集合(GC Roots)中掃描活動對象右核,并將每個 活動對象復制到空閑面(使得活動對象所占的內存之間沒有空閑洞)慧脱,這樣空閑面變成了對象面,原來的對象面變成了空閑面贺喝,程序會在新的對象面中分配內存菱鸥。
標記清除算法:
這是最基礎的垃圾回收算法,之所以說它是最基礎的是因為它最容易實現(xiàn)躏鱼,思想也是最簡單的氮采。標記-清除算法分為兩個階段:標記階段和清除階段。標記階段的任務是標記出所有需要被回收的對象染苛,清除階段就是回收被標記的對象所占用的空間鹊漠。具體過程如下圖所示:
從圖中可以很容易看出標記-清除算法實現(xiàn)起來比較容易,但是有一個比較嚴重的問題就是容易產(chǎn)生內存碎片茶行,碎片太多可能會導致后續(xù)過程中需要為大對象分配空間時無法找到足夠的空間而提前觸發(fā)新的一次垃圾收集動作躯概。
標記-清除算法采用從根集合(GC Roots)進行掃描,對存活的對象進行標記拢军,標記完畢后楞陷,再掃描整個空間中未被標記的對象,進行回收茉唉,如下圖所示固蛾。標記-清除算法不需要進行對象的移動结执,只需對不存活的對象進行處理,在存活對象比較多的情況下極為高效艾凯,但由于標記-清除算法直接回收不存活的對象献幔,因此會造成內存碎片。
標記壓縮算法
為了解決Copying算法的缺陷趾诗,充分利用內存空間蜡感,提出了Mark-Compact算法。該算法標記階段和Mark-Sweep一樣恃泪,但是在完成標記之后郑兴,它不是直接清理可回收對象,而是將存活對象都向一端移動(美團面試題目贝乎,記住是完成標記之后情连,先不清理,先移動再清理回收對象)览效,然后清理掉端邊界以外的內存(美團問過)?
標記-整理算法采用標記-清除算法一樣的方式進行對象的標記却舀,但在清除時不同,在回收不存活的對象占用的空間后锤灿,會將所有的存活對象往左端空閑空間移動挽拔,并更新對應的指針。標記-整理算法是在標記-清除算法的基礎上但校,又進行了對象的移動螃诅,因此成本更高,但是卻解決了內存碎片的問題始腾。具體流程見下圖:
2州刽、?敘述JVM常用的調優(yōu)參數(shù)
-Xmx:最大JVM可用內存, 例:-Xmx4g
-Xms:最小JVM可用內存浪箭, 例:Xms4g
-Xmn:年輕代內存大小穗椅,例:-Xmn2560m
-XX:PermSize:永久代內存大小,該值太大會導致fullGC時間過長奶栖,太小將增加fullGC頻率匹表,例:-XX:PermSize=128m
-Xss:線程棧大小,太大將導致JVM可建的線程數(shù)量減少宣鄙,例:-Xss256k
-XX:+DisableExplicitGC:禁止手動fullGC袍镀,如果配置,則System.gc()將無效冻晤,比如在為DirectByteBuffer分配空間過程中發(fā)現(xiàn)直接內存不足時會顯式調用System.gc()
-XX:+UseConcMarkSweepGC:一般PermGen是不會被GC苇羡,如果希望PermGen永久代也能被GC,則需要配置該參數(shù)
-XX:+CMSParallelRemarkEnabled:GC進行時標記可回收對象時可以并行remark-XX:+UseCMSCompactAtFullCollection 表示在fullGC之后進行壓縮鼻弧,CMS默認不壓縮空間
-XX:LargePageSizeInBytes:為java堆內存設置內存頁大小设江,例:-XX:LargePageSizeInBytes=128m
-XX:+UseFastAccessorMethods:對原始類型進行快速優(yōu)化
-XX:+UseCMSInitiatingOccupancyOnly:關閉預期開始的晉升率的統(tǒng)計
-XX:CMSInitiatingOccupancyFraction:使用cms作為垃圾回收锦茁,并設置GC百分比,例:-XX:CMSInitiatingOccupancyFraction=70(使用70%后開始CMS收集)
-XX:+PrintGCDetails:打印GC的詳細信息
-XX:+PrintGCDateStamps:打印GC的時間戳
-Xloggc:指定GC文件路徑
新生代與老年代的垃圾回收器組合方式
JVM常用的分析工具:
jps:用來查看運行的所有jvm進程叉存;
jinfo:查看進程的運行環(huán)境參數(shù)码俩,主要是jvm命令行參數(shù);
jstat:對jvm應用程序的資源和性能進行實時監(jiān)控歼捏;
jstack:查看所有線程的運行狀態(tài)稿存;
jmap:查看jvm占用物理內存的狀態(tài);
jhat:+UseParNew
jconsole:
jvisualvm:
jps:Java virutal machine Process Status tool瞳秽,
jps [-q] [-mlvV] [<hostid>]
-q:靜默模式瓣履;
-v:顯示傳遞給jvm的命令行參數(shù);
-m:輸出傳入main方法的參數(shù)寂诱;
-l:輸出main類或jar完全限定名稱拂苹;
-V:顯示通過flag文件傳遞給jvm的參數(shù);
[<hostid>]:主機id痰洒,默認為localhost;
jinfo:輸出給定的java進程的所有配置信息浴韭;
jinfo [option] <pid>
-flags:to print VM flags
-sysprops:to print Java system properties
-flag <name>:to print the value of the named VM flag
jstack:查看指定的java進程的線程棧的相關信息丘喻;
jstack [-l] <pid>
jstack -F [-m] [-l] <pid>
-l:long listings,會顯示額外的鎖信息念颈,因此泉粉,發(fā)生死鎖時常用此選項;
-m:混合模式榴芳,既輸出java堆棧信息嗡靡,也輸出C/C++堆棧信息;
-F:當使用“jstack -l PID"無響應窟感,可以使用-F強制輸出信息讨彼;
jstat:輸出指定的java進程的統(tǒng)計信息
jstat -help|-options
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
# jstat -options
-class:class loader
-compiler:JIT
-gc:gc
-gccapacity:統(tǒng)計堆中各代的容量
-gccause:
-gcmetacapacity
-gcnew:新生代
-gcnewcapacity
-gcold:老年代
-gcoldcapacity
-gcutil
-printcompilation
[<interval> [<count>]]
interval:時間間隔,單位是毫秒柿祈;
count:顯示的次數(shù)哈误;
-gc:
YGC:新生代的垃圾回收次數(shù);
YGCT:新生代垃圾回收消耗的時長躏嚎;
FGC:Full GC的次數(shù)蜜自;
FGCT:Full GC消耗的時長;
GCT:GC消耗的總時長卢佣;
jmap:Memory Map, 用于查看堆內存的使用狀態(tài)重荠;
jhat:Java Heap Analysis Tool
jmap [option] <pid>
查看堆空間的詳細信息:
jmap -heap <pid>
查看堆內存中的對象的數(shù)目:
jmap -histo[:live] <pid>
live:只統(tǒng)計活動對象;
保存堆內存數(shù)據(jù)至文件中虚茶,而后使用jvisualvm或jhat進行查看:
jmap -dump:<dump-options> <pid>
dump-options:
live? ? ? ? dump only live objects; if not specified, all objects in the heap are dumped.
format=b? ? binary format
file=<file>? dump heap to <file>
Tomcat的常用優(yōu)化配置:
(1) 內存空間:
/etc/sysconfig/tomcat, /etc/tomcat/tomcat.conf
JAVA_OPTS="-server -Xms32g -Xmx32g -XX:NewSize= -XX:MaxNewSize= "
-server:服務器模式
-Xms:堆內存初始化大懈曷场仇参;
-Xmx:堆內存空間上限;
-XX:NewSize=:新生代空間初始化大熊癖恕冈敛;
-XX:MaxNewSize=:新生代空間最大值;
(2) 線程池設置:
<Connector port="8080" protocol="HTTP/1.1"? connectionTimeout="20000" redirectPort="8443" />
常用屬性:
maxThreads:最大線程數(shù)鸣皂;
minSpareThreads:最小空閑線程數(shù)抓谴;
maxSpareThreads:最大空閑線程數(shù);
acceptCount:等待隊列的最大長度寞缝;
URIEncoding:URI地址編碼格式癌压,建議使用UTF-8;
enableLookups:是否啟用dns解析荆陆,建議禁用滩届;
compression:是否啟用傳輸壓縮機制,建議“on";
compressionMinSize:啟用壓縮傳輸?shù)臄?shù)據(jù)流最小值被啼,單位是字節(jié)帜消;
compressableMimeType:定義啟用壓縮功能的MIME類型;
text/html, text/xml, text/css, text/javascript
指定垃圾收集器:
? ? ? ? -XX:
? ? ? ? ? ? UseSerialGC:運行于Client模式下浓体,新生代是Serial, 老年代使用SerialOld
? ? ? ? ? ? UseParNewGC:新生代使用ParNew泡挺,老年代使用SerialOld
? ? ? ? ? ? UseParalellGC:運行于server模式下,新生代使用Serial Scavenge, 老年代使用SerialOld
? ? ? ? ? ? UseParalellOldGC:新生代使用Paralell Scavenge, 老年代使用Paralell Old
? ? ? ? ? ? UseConcMarkSweepGC:新生代使用ParNew, 老年代優(yōu)先使用CMS命浴,備選方式為Serial Old
? ? ? ? ? ? ? ? CMSInitiatingOccupancyFraction:設定老年代空間占用比例達到多少后觸發(fā)回收操作娄猫,默認為68%;
? ? ? ? ? ? ? ? UseCMSCompactAtFullCollection:CMS完成內存回收后是否要進行內存碎片整理生闲;
? ? ? ? ? ? ? ? CMSFullGCsBeforeCompaction:在多少次回收后執(zhí)行一次內存碎片整理媳溺;
? ? ? ? ? ? ParalellGCThreads:并行GC線程的數(shù)量;
定義JVM進程運行特性的參數(shù)可大體分為兩類:
? ? ? ? 系統(tǒng)屬性:system properties
? ? ? ? 標志:-X, -XX:
評估標準:
? ? ? ? 吞吐量優(yōu)先:-XX:GCTimeRatio=n, 設置垃圾回收的時長占程序運行時長的百分比碍讯;
? ? ? ? 暫停時間優(yōu)先:-XX:MaxGcPauseRatio=n
? ? 顯示垃圾回收的統(tǒng)計信息:
? ? ? ? -XX:+PrintGC
? ? ? ? -XX:+PrintGCDetails
? ? ? ? -XX:+PrintGCTimeStamps
3悬蔽、?JAVA進程發(fā)生OOM如何調優(yōu)
?OOM,全稱“Out Of Memory”冲茸,翻譯成中文就是“內存用完了”屯阀,來源于java.lang.OutOfMemoryError≈崾酰看下關于的官方說明:?Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector. 意思就是說难衰,當JVM因為沒有足夠的內存來為對象分配空間并且垃圾回收器也已經(jīng)沒有空間可回收時,就會拋出這個error(注:非exception逗栽,因為這個問題已經(jīng)嚴重到不足以被應用處理)盖袭。
2)為什么會OOM?
為什么會沒有內存了呢?原因不外乎有兩點:
1)分配的少了:比如虛擬機本身可使用的內存(一般通過啟動時的VM參數(shù)指定)太少鳄虱。
2)應用用的太多弟塞,并且用完沒釋放,浪費了拙已。此時就會造成內存泄露或者內存溢出决记。
內存泄露:申請使用完的內存沒有釋放,導致虛擬機不能再次使用該內存倍踪,此時這段內存就泄露了系宫,因為申請者不用了,而又不能被虛擬機分配給別人用建车。
內存溢出:申請的內存超出了JVM能提供的內存大小扩借,此時稱之為溢出。
在之前沒有垃圾自動回收的日子里缤至,比如C語言和C++語言潮罪,我們必須親自負責內存的申請與釋放操作,如果申請了內存领斥,用完后又忘記了釋放嫉到,比如C++中的new了但是沒有delete,那么就可能造成內存泄露月洛。偶爾的內存泄露可能不會造成問題屯碴,而大量的內存泄露可能會導致內存溢出。
而在Java語言中膊存,由于存在了垃圾自動回收機制,所以忱叭,我們一般不用去主動釋放不用的對象所占的內存隔崎,也就是理論上來說,是不會存在“內存泄露”的韵丑。但是爵卒,如果編碼不當,比如撵彻,將某個對象的引用放到了全局的Map中钓株,雖然方法結束了,但是由于垃圾回收器會根據(jù)對象的引用情況來回收內存陌僵,導致該對象不能被及時的回收轴合。如果該種情況出現(xiàn)次數(shù)多了,就會導致內存溢出碗短,比如系統(tǒng)中經(jīng)常使用的緩存機制受葛。Java中的內存泄露,不同于C++中的忘了delete,往往是邏輯上的原因泄露总滩。
3)OOM的類型
JVM內存模型:
按照JVM規(guī)范纲堵,JAVA虛擬機在運行時會管理以下的內存區(qū)域:
程序計數(shù)器:當前線程執(zhí)行的字節(jié)碼的行號指示器,線程私有
JAVA虛擬機棧:Java方法執(zhí)行的內存模型闰渔,每個Java方法的執(zhí)行對應著一個棧幀的進棧和出棧的操作席函。
本地方法棧:類似“ JAVA虛擬機棧?”,但是為native方法的運行提供內存環(huán)境冈涧。
JAVA堆:對象內存分配的地方茂附,內存垃圾回收的主要區(qū)域,所有線程共享炕舵『沃可分為新生代,老生代咽筋。
方法區(qū):用于存儲已經(jīng)被JVM加載的類信息溶推、常量、靜態(tài)變量奸攻、即時編譯器編譯后的代碼等數(shù)據(jù)蒜危。Hotspot中的“永久代”。
運行時常量池:方法區(qū)的一部分睹耐,存儲常量信息辐赞,如各種字面量、符號引用等硝训。
直接內存:并不是JVM運行時數(shù)據(jù)區(qū)的一部分响委,?可直接訪問的內存,?比如NIO會用到這部分窖梁。
按照JVM規(guī)范赘风,除了程序計數(shù)器不會拋出OOM外,其他各個內存區(qū)域都可能會拋出OOM纵刘。
最常見的OOM情況有以下三種:
java.lang.OutOfMemoryError: Java heap space ------>java堆內存溢出邀窃,此種情況最常見,一般由于內存泄露或者堆的大小設置不當引起假哎。對于內存泄露瞬捕,需要通過內存監(jiān)控軟件查找程序中的泄露代碼,而堆大小可以通過虛擬機參數(shù)-Xms,-Xmx等修改舵抹。
java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出肪虎,即方法區(qū)溢出了,一般出現(xiàn)于大量Class或者jsp頁面掏父,或者采用cglib等反射機制的情況笋轨,因為上述情況會產(chǎn)生大量的Class信息存儲于方法區(qū)秆剪。此種情況可以通過更改方法區(qū)的大小來解決,使用類似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改爵政。另外仅讽,過多的常量尤其是字符串也會導致方法區(qū)溢出。
java.lang.StackOverflowError ------>?不會拋OOM error钾挟,但也是比較常見的Java內存溢出洁灵。JAVA虛擬機棧溢出,一般是由于程序中存在死循環(huán)或者深度遞歸調用造成的掺出,棧大小設置太小也會出現(xiàn)此種溢出徽千。可以通過虛擬機參數(shù)-Xss來設置棧的大小汤锨。
4)OOM分析--heapdump
要dump堆的內存鏡像双抽,可以采用如下兩種方式:
設置JVM參數(shù)-XX:+HeapDumpOnOutOfMemoryError,設定當發(fā)生OOM時自動dump出堆信息闲礼。不過該方法需要JDK5以上版本牍汹。
使用JDK自帶的jmap命令。"jmap -dump:format=b,file=heap.bin <pid>" ? 其中pid可以通過jps獲取柬泽。
dump堆內存信息后慎菲,需要對dump出的文件進行分析,從而找到OOM的原因锨并。常用的工具有:
mat: eclipse memory analyzer, 基于eclipse RCP的內存分析工具露该。詳細信息參見:http://www.eclipse.org/mat/,推薦使用第煮。 ??
jhat:JDK自帶的java heap analyze tool解幼,可以將堆中的對象以html的形式顯示出來,包括對象的數(shù)量包警,大小等等书幕,并支持對象查詢語言OQL,分析相關的應用后揽趾,可以通過http://localhost:7000來訪問分析結果。不推薦使用苛骨,因為在實際的排查過程中篱瞎,一般是先在生產(chǎn)環(huán)境 dump出文件來,然后拉到自己的開發(fā)機器上分析痒芝,所以俐筋,不如采用高級的分析工具比如前面的mat來的高效。
這個鏈接:http://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html中提供了一個采用mat分析的例子?严衬。
注意:因為JVM規(guī)范沒有對dump出的文件的格式進行定義澄者,所以不同的虛擬機產(chǎn)生的dump文件并不是一樣的。在分析時,需要針對不同的虛擬機的輸出采用不同的分析工具(當然粱挡,有的工具可以兼容多個虛擬機的格式)赠幕。IBM HeapAnalyzer也是分析heap的一個常用的工具。
5)小結
涉及到的虛擬機的技術或者工具询筏,往往需要考慮到虛擬機規(guī)范以及不同的虛擬機實現(xiàn)榕堰。尤其是針對虛擬機調優(yōu)時,往往需要針對虛擬機在某些方面的實現(xiàn)策略來考慮嫌套,比如逆屡,不同的虛擬機的垃圾回收算法是不一樣的膏潮,而這直接影響了虛擬機某些參數(shù)的設置痒谴,以達到虛擬機的最佳性能。
而針對JVM運行時的分析與診斷驼卖,則需要掌握分析基本方法痹筛,針對具體情況莺治,運用虛擬機的原理,具體分析味混。一句話产雹,水很深啊