JVM重要知識點整理和學(xué)習(xí)

JVM是虛擬機(jī)惧财,也是一種規(guī)范巡扇,他遵循著馮·諾依曼體系結(jié)構(gòu)的設(shè)計原理。馮·諾依曼體系結(jié)構(gòu)中垮衷,指出計算機(jī)處理的數(shù)據(jù)和指令都是二進(jìn)制數(shù)厅翔,采用存儲程序方式不加區(qū)分的存儲在同一個存儲器里,并且順序執(zhí)行搀突,指令由操作碼和地址碼組成刀闷,操作碼決定了操作類型和所操作的數(shù)的數(shù)字類型,地址碼則指出地址碼和操作數(shù)仰迁。從dos到window8甸昏,從unix到ubuntu和CentOS,還有MAC OS等等徐许,不同的操作系統(tǒng)指令集以及數(shù)據(jù)結(jié)構(gòu)都有著差異施蜜,而JVM通過在操作系統(tǒng)上建立虛擬機(jī),自己定義出來的一套統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)和操作指令雌隅,把同一套語言翻譯給各大主流的操作系統(tǒng)翻默,實現(xiàn)了跨平臺運(yùn)行,可以說JVM是java的核心恰起,是java可以一次編譯到處運(yùn)行的本質(zhì)所在修械。

我研究學(xué)習(xí)了JVM的組成和運(yùn)行原理,JVM的統(tǒng)一數(shù)據(jù)格式規(guī)范村缸、字節(jié)碼文件結(jié)構(gòu)祠肥,JVM關(guān)于內(nèi)存的管理。

一梯皿、JVM的組成和運(yùn)行原理

JVM的畢竟是個虛擬機(jī)仇箱,是一種規(guī)范,雖說符合馮諾依曼的計算機(jī)設(shè)計理念东羹,但是他并不是實體計算機(jī)剂桥,所以他的組成也不是什么存儲器,控制器属提,運(yùn)算器权逗,輸入輸出設(shè)備。在我看來冤议,JVM放在運(yùn)行在真實的操作系統(tǒng)中表現(xiàn)的更像應(yīng)用或者說是進(jìn)程斟薇,他的組成可以理解為JVM這個進(jìn)程有哪些功能模塊,而這些功能模塊的運(yùn)作可以看做是JVM的運(yùn)行原理恕酸。JVM有多種實現(xiàn)堪滨,例如Oracle的JVM,HP的JVM和IBM的JVM等蕊温,而在本文中研究學(xué)習(xí)的則是使用最廣泛的Oracle的HotSpot JVM袱箱。

1.JVM在JDK中的位置遏乔。

JDK是java開發(fā)的必備工具箱,JDK其中有一部分是JRE发笔,JRE是JAVA運(yùn)行環(huán)境盟萨,JVM則是JRE最核心的部分。我從oracle.com截取了一張關(guān)于JDK Standard Edtion的組成圖了讨,

從最底層的位置可以看出來JVM有多重要捻激,而實際項目中JAVA應(yīng)用的性能優(yōu)化,OOM等異常的處理最終都得從JVM這兒來解決量蕊。HotSpot是Oracle關(guān)于JVM的商標(biāo)铺罢,區(qū)別于IBM,HP等廠商開發(fā)的JVM残炮。Java HotSpot Client VM和Java HotSpot Server VM是JDK關(guān)于JVM的兩種不同的實現(xiàn)韭赘,前者可以減少啟動時間和內(nèi)存占用,而后者則提供更加優(yōu)秀的程序運(yùn)行速度(參考自:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/index.html势就,該文檔有關(guān)于各個版本的JVM的介紹)泉瞻。在命令行,通過java -version可以查看關(guān)于當(dāng)前機(jī)器JVM的信息苞冯,下面是我在Win8系統(tǒng)上執(zhí)行命令的截圖袖牙,

可以看出我裝的是build 20.13-b02版本,HotSpot 類型Server模式的JVM舅锄。

2.JVM的組成

JVM由4大部分組成:ClassLoader鞭达,Runtime Data Area,Execution Engine皇忿,Native Interface畴蹭。

我從CSDN找了一張描述JVM大致結(jié)構(gòu)的圖:

2.1.ClassLoader是負(fù)責(zé)加載class文件,class文件在文件開頭有特定的文件標(biāo)示鳍烁,并且ClassLoader只負(fù)責(zé)class文件的加載叨襟,至于它是否可以運(yùn)行,則由Execution Engine決定幔荒。

2.2.Native Interface是負(fù)責(zé)調(diào)用本地接口的糊闽。他的作用是調(diào)用不同語言的接口給JAVA用,他會在Native Method Stack中記錄對應(yīng)的本地方法爹梁,然后調(diào)用該方法時就通過Execution Engine加載對應(yīng)的本地lib右犹。原本多于用一些專業(yè)領(lǐng)域,如JAVA驅(qū)動姚垃,地圖制作引擎等念链,現(xiàn)在關(guān)于這種本地方法接口的調(diào)用已經(jīng)被類似于Socket通信,WebService等方式取代。

2.3.Execution Engine是執(zhí)行引擎钓账,也叫Interpreter。Class文件被加載后絮宁,會把指令和數(shù)據(jù)信息放入內(nèi)存中梆暮,Execution Engine則負(fù)責(zé)把這些命令解釋給操作系統(tǒng)。

2.4.Runtime Data Area則是存放數(shù)據(jù)的绍昂,分為五部分:Stack啦粹,Heap,Method Area窘游,PC Register唠椭,Native Method Stack。幾乎所有的關(guān)于java內(nèi)存方面的問題忍饰,都是集中在這塊贪嫂。下圖是javapapers.com上關(guān)于Run-time Data Areas的描述:

可以看出它把Method Area化為了Heap的一部分,javapapers.com中認(rèn)為Method Area是Heap的邏輯區(qū)域艾蓝,但這取決于JVM的實現(xiàn)者力崇,而HotSpot JVM中把Method Area劃分為非堆內(nèi)存,顯然是不包含在Heap中的赢织。下圖是javacodegeeks.com中亮靴,2014年9月刊出的一片博文中關(guān)于Runtime Data Area的劃分,其中指出于置,NonHeap包含PermGen和Code Cache茧吊,PermGen包含Method Area,而且PermGen在JAVA SE 8中已經(jīng)不再用了。查閱資料(https://abhirockzz.wordpress.com/2014/03/18/java-se-8-is-knocking-are-you-there/)得知八毯,java8中PermGen已經(jīng)從JVM中移除并被MetaSpace取代搓侄,java8中也不會見到OOM:PermGen Space的異常。目前Runtime Data Area可以用下圖描述它的組成:

2.4.1.Stack是java棧內(nèi)存宪彩,它等價于C語言中的棧休讳,棧的內(nèi)存地址是不連續(xù)的,每個線程都擁有自己的棧尿孔。棧里面存儲著的是StackFrame俊柔,在《JVM Specification》中文版中被譯作java虛擬機(jī)框架,也叫做棧幀活合。StackFrame包含三類信息:局部變量雏婶,執(zhí)行環(huán)境,操作數(shù)棧白指。局部變量用來存儲一個類的方法中所用到的局部變量留晚。執(zhí)行環(huán)境用于保存解析器對于java字節(jié)碼進(jìn)行解釋過程中需要的信息,包括:上次調(diào)用的方法、局部變量指針和操作數(shù)棧的棧頂和棧底指針错维。操作數(shù)棧用于存儲運(yùn)算所需要的操作數(shù)和結(jié)果奖地。StackFrame在方法被調(diào)用時創(chuàng)建,在某個線程中赋焕,某個時間點上参歹,只有一個框架是活躍的,該框架被稱為Current Frame隆判,而框架中的方法被稱為Current Method犬庇,其中定義的類為Current Class。局部變量和操作數(shù)棧上的操作總是引用當(dāng)前框架侨嘀。當(dāng)Stack Frame中方法被執(zhí)行完之后臭挽,或者調(diào)用別的StackFrame中的方法時,則當(dāng)前棧變?yōu)榱硗庖粋€StackFrame咬腕。Stack的大小是由兩種類型欢峰,固定和動態(tài)的,動態(tài)類型的椑赏簦可以按照線程的需要分配赤赊。 下面兩張圖是關(guān)于棧之間關(guān)系以及棧和非堆內(nèi)存的關(guān)系基本描述(來自http://www.programering.com/a/MzM3QzNwATA.html):

2.4.2.Heap是用來存放對象信息的,和Stack不同煞赢,Stack代表著一種運(yùn)行時的狀態(tài)抛计。換句話說,棧是運(yùn)行時單位照筑,解決程序該如何執(zhí)行的問題吹截,而堆是存儲的單位,解決數(shù)據(jù)存儲的問題凝危。Heap是伴隨著JVM的啟動而創(chuàng)建波俄,負(fù)責(zé)存儲所有對象實例和數(shù)組的。堆的存儲空間和棧一樣是不需要連續(xù)的蛾默,它分為Young Generation和Old Generation(也叫Tenured?Generation)兩大部分懦铺。Young Generation分為Eden和Survivor,Survivor又分為From Space和 ToSpace支鸡。

和Heap經(jīng)常一起提及的概念是PermanentSpace冬念,它是用來加載類對象的專門的內(nèi)存區(qū),是非堆內(nèi)存牧挣,和Heap一起組成JAVA內(nèi)存急前,它包含MethodArea區(qū)(在沒有CodeCache的HotSpotJVM實現(xiàn)里,則MethodArea就相當(dāng)于GenerationSpace)瀑构。在JVM初始化的時候裆针,我們可以通過參數(shù)來分別指定,PermanentSpace的大小、堆的大小世吨、以及Young Generation和Old Generation的比值澡刹、Eden區(qū)和From Space的比值,從而來細(xì)粒度的適應(yīng)不同JAVA應(yīng)用的內(nèi)存需求耘婚。

2.4.3.PC Register是程序計數(shù)寄存器像屋,每個JAVA線程都有一個單獨(dú)的PC Register,他是一個指針边篮,由Execution Engine讀取下一條指令。如果該線程正在執(zhí)行java方法奏甫,則PC Register存儲的是 正在被執(zhí)行的指令的地址戈轿,如果是本地方法,PC Register的值沒有定義阵子。PC寄存器非常小思杯,只占用一個字寬,可以持有一個returnAdress或者特定平臺的一個指針挠进。

2.4.4.Method Area在HotSpot JVM的實現(xiàn)中屬于非堆區(qū)色乾,非堆區(qū)包括兩部分:Permanet Generation和Code Cache,而Method Area屬于Permanert Generation的一部分领突。Permanent Generation用來存儲類信息暖璧,比如說:class definitions,structures君旦,methods澎办, field, method (data and code) 和 constants金砍。Code Cache用來存儲Compiled Code局蚀,即編譯好的本地代碼,在HotSpot JVM中通過JIT(Just In Time) Compiler生成恕稠,JIT是即時編譯器琅绅,他是為了提高指令的執(zhí)行效率,把字節(jié)碼文件編譯成本地機(jī)器代碼鹅巍,如下圖:

引用一個經(jīng)典的案例來理解Stack千扶,Heap和Method Area的劃分,就是Sring a=”xx”昆著;Stirng b=”xx”县貌,問是否a==b? 首先==符號是用來判斷兩個對象的引用地址是否相同,而在上面的題目中凑懂,a和b按理來說申請的是Stack中不同的地址煤痕,但是他們指向Method Area中Runtime Constant Pool的同一個地址,按照網(wǎng)上的解釋,在a賦值為“xx”時摆碉,會在Runtime Contant Pool中生成一個String Constant塘匣,當(dāng)b也賦值為“xx”時,那么會在常量池中查看是否存在值為“xx”的常量巷帝,存在的話忌卤,則把b的指針也指向“xx”的地址,而不是新生成一個String Constant楞泼。我查閱了網(wǎng)絡(luò)上大家關(guān)于String Constant的存儲的說說法驰徊,存在略微差別的是,它存儲在哪里堕阔,有人說Heap中會分配出一個常量池棍厂,用來存儲常量,所有線程共享它超陆。而有人說常量池是Method Area的一部分牺弹,而Method Area屬于非堆內(nèi)存,那怎么能說常量池存在于堆中时呀?

我認(rèn)為张漂,其實兩種理解都沒錯。Method Area的確從邏輯上講可以是Heap的一部分谨娜,在某些JVM實現(xiàn)里從堆上開辟一塊存儲空間來記錄常量是符合JVM常量池設(shè)計目的的航攒,所以前一種說法沒問題。對于后一種說法趴梢,HotSpot JVM的實現(xiàn)中的確是把方法區(qū)劃分為了非堆內(nèi)存屎债,意思就是它不在堆上。我在HotSpot JVM做了個簡單的實驗垢油,定義多個常量之后盆驹,程序拋出OOM:PermGen Space異常,印證了JVM實現(xiàn)中常量池是在Permanent Space中的說法滩愁。但是躯喇,我的JDK版本是1.6的。查閱資料硝枉,JDK1.7中InternedStrings已經(jīng)不再存儲在PermanentSpace中廉丽,而是放到了Heap中;JDK8中PermanentSpace已經(jīng)被完全移除妻味,InternedStrings也被放到了MetaSpace中(如果出現(xiàn)內(nèi)存溢出正压,會報OOM:MetaSpace,這里有個關(guān)于兩者性能對比的文章:http://blog.csdn.net/zhyhang/article/details/17246223)责球。?所以焦履,仁者見仁拓劝,智者見智,一個饅頭足以引發(fā)血案嘉裤,就算是同一個商家的JVM郑临,畢竟JDK版本在更新,或許正如StackOverFlow上大神們所說屑宠,對于理解JVM Runtime Data Area這一部分的劃分邏輯厢洞,還是去看對應(yīng)版本的JDK源碼比較靠譜,或者是參考不同的版本JVM Specification(http://docs.oracle.com/javase/specs/)典奉。

2.4.5.Native Method Stack是供本地方法(非java)使用的棧躺翻。每個線程持有一個Native Method Stack。

3.JVM的運(yùn)行原理簡介

Java?程序被javac工具編譯為.class字節(jié)碼文件之后卫玖,我們執(zhí)行java命令获枝,該class文件便被JVM的Class Loader加載,可以看出JVM的啟動是通過JAVA Path下的java.exe或者java進(jìn)行的骇笔。JVM的初始化、運(yùn)行到結(jié)束大概包括這么幾步:

調(diào)用操作系統(tǒng)API判斷系統(tǒng)的CPU架構(gòu)嚣崭,根據(jù)對應(yīng)CPU類型尋找位于JRE目錄下的/lib/jvm.cfg文件笨触,然后通過該配置文件找到對應(yīng)的jvm.dll文件(如果我們參數(shù)中有-server或者-client, 則加載對應(yīng)參數(shù)所指定的jvm.dll雹舀,啟動指定類型的JVM)芦劣,初始化jvm.dll并且掛接到JNIENV結(jié)構(gòu)的實例上,之后就可以通過JNIENV實例裝載并且處理class文件了说榆。class文件是字節(jié)碼文件虚吟,它按照J(rèn)VM的規(guī)范,定義了變量签财,方法等的詳細(xì)信息串慰,JVM管理并且分配對應(yīng)的內(nèi)存來執(zhí)行程序,同時管理垃圾回收唱蒸。直到程序結(jié)束邦鲫,一種情況是JVM的所有非守護(hù)線程停止,一種情況是程序調(diào)用System.exit()神汹,JVM的生命周期也結(jié)束庆捺。

關(guān)于JVM如何管理分配內(nèi)存,我通過class文件和垃圾回收兩部分進(jìn)行了學(xué)習(xí)屁魏。

二滔以、JVM的內(nèi)存管理和垃圾回收

JVM中的內(nèi)存管理主要是指JVM對于Heap的管理,這是因為Stack氓拼,PC Register和Native Method Stack都是和線程一樣的生命周期你画,在線程結(jié)束時自然可以被再次使用抵碟。雖然說,Stack的管理不是重點撬即,但是也不是完全不講究的立磁。

1.棧的管理

JVM允許棧的大小是固定的或者是動態(tài)變化的。在Oracle的關(guān)于參數(shù)設(shè)置的官方文檔中有關(guān)于Stack的設(shè)置(http://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/jrdocs/refman/optionX.html#wp1024112)剥槐,是通過-Xss來設(shè)置其大小唱歧。關(guān)于Stack的默認(rèn)大小對于不同機(jī)器有不同的大小,并且不同廠商或者版本號的jvm的實現(xiàn)其大小也不同粒竖,如下表是HotSpot的默認(rèn)大新馈:

Platform

Default

Windows IA32

64 KB

Linux IA32

128 KB

Windows x86_64

128 KB

Linux x86_64

256 KB

Windows IA64

320 KB

Linux IA64

1024 KB (1 MB)

Solaris Sparc

512 KB

我們一般通過減少常量,參數(shù)的個數(shù)來減少棧的增長蕊苗,在程序設(shè)計時沿后,我們把一些常量定義到一個對象中,然后來引用他們可以體現(xiàn)這一點朽砰。另外尖滚,少用遞歸調(diào)用也可以減少棧的占用。? ??棧是不需要垃圾回收的瞧柔,盡管說垃圾回收是java內(nèi)存管理的一個很熱的話題漆弄,棧中的對象如果用垃圾回收的觀點來看,他永遠(yuǎn)是live狀態(tài)造锅,是可以reachable的撼唾,所以也不需要回收,他占有的空間隨著Thread的結(jié)束而釋放哥蔚。(參考自:http://stackoverflow.com/questions/20030120/java-default-stack-size

關(guān)于棧一般會發(fā)生以下兩種異常:

1.當(dāng)線程中的計算所需要的棧超過所允許大小時倒谷,會拋出StackOverflowError。

2.當(dāng)Java棧試圖擴(kuò)展時糙箍,沒有足夠的存儲器來實現(xiàn)擴(kuò)展渤愁,JVM會報OutOfMemoryError。? ??我針對棧進(jìn)行了實驗深夯,由于遞歸的調(diào)用可以致使棧的引用增加猴伶,導(dǎo)致溢出,所以設(shè)計代碼如下:

我的機(jī)器是x86_64系統(tǒng)塌西,所以Stack的默認(rèn)大小是128KB他挎,上述程序在運(yùn)行時會報錯:

而當(dāng)我在Eclipse中調(diào)整了-Xss參數(shù)到3M之后,該異常消失捡需。

另外棧上有一點得注意的是办桨,對于本地代碼調(diào)用,可能會在棧中申請內(nèi)存站辉,比如C調(diào)用malloc()呢撞,而這種情況下损姜,GC是管不著的,需要我們在程序中殊霞,手動管理棧內(nèi)存摧阅,使用free()方法釋放內(nèi)存萄唇。

2.堆的管理

堆的管理要比棧管理復(fù)雜的多奈偏,我通過堆的各部分的作用涵亏、設(shè)置托享,以及各部分可能發(fā)生的異常,以及如何避免各部分異常進(jìn)行了學(xué)習(xí)窖杀。

上圖是 Heap和PermanentSapce的組合圖守屉,其中?Eden區(qū)里面存著是新生的對象讥邻,F(xiàn)rom Space和To Space中存放著是每次垃圾回收后存活下來的對象 拦英,所以每次垃圾回收后蜒什,Eden區(qū)會被清空。?存活下來的對象先是放到From Space疤估,當(dāng)From Space滿了之后移動到To?Space灾常。當(dāng)To Space滿了之后移動到Old Space。Survivor的兩個區(qū)是對稱的铃拇,沒先后關(guān)系钞瀑,所以同一個區(qū)中可能同時存在從Eden復(fù)制過來 對象,和從前一個Survivor復(fù)制過來的對象锚贱,而復(fù)制到年老區(qū)的只有從第一個Survivor復(fù)制過來的對象。而且关串,Survivor區(qū)總有一個是空的拧廊。同時,根據(jù)程序需要晋修,Survivor區(qū)是可以配置為多個的(多于兩個)吧碾,這樣可以增加對象在年輕代中的存在時間,減少被放到年老代的可能墓卦。

Old Space中則存放生命周期比較長的對象倦春,而且有些比較大的新生對象也放在Old Space中。

堆的大小通過-Xms和-Xmx來指定最小值和最大值落剪,通過-Xmn來指定Young Generation的大姓霰尽(一些老版本也用-XX:NewSize指定), 即上圖中的Eden加FromSpace和ToSpace的總大小忠怖。然后通過-XX:NewRatio來指定Eden區(qū)的大小呢堰,在Xms和Xmx相等的情況下,該參數(shù)不需要設(shè)置凡泣。通過-XX:SurvivorRatio來設(shè)置Eden和一個Survivor區(qū)的比值枉疼。(參考自博文:http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html

堆異常分為兩種皮假,一種是Out of Memory(OOM),一種是Memory Leak(ML)骂维。Memory Leak最終將導(dǎo)致OOM惹资。實際應(yīng)用中表現(xiàn)為:從Console看,內(nèi)存監(jiān)控曲線一直在頂部航闺,程序響應(yīng)慢褪测,從線程看,大部分的線程在進(jìn)行GC来颤,占用比較多的CPU汰扭,最終程序異常終止,報OOM福铅。OOM發(fā)生的時間不定萝毛,有短的一個小時,有長的10天一個月的滑黔。關(guān)于異常的處理笆包,確定OOM/ML異常后,一定要注意保護(hù)現(xiàn)場略荡,可以dump heap庵佣,如果沒有現(xiàn)場則開啟GCFlag收集垃圾回收日志,然后進(jìn)行分析汛兜,確定問題所在巴粪。如果問題不是ML的話,一般通過增加Heap粥谬,增加物理內(nèi)存來解決問題肛根,是的話,就修改程序邏輯漏策。

3.垃圾回收

JVM中會在以下情況觸發(fā)回收:對象沒有被引用派哲,作用域發(fā)生未捕捉異常,程序正常執(zhí)行完畢掺喻,程序執(zhí)行了System.exit()芭届,程序發(fā)生意外終止。

JVM中標(biāo)記垃圾使用的算法是一種根搜索算法感耙。簡單的說褂乍,就是從一個叫GC Roots的對象開始,向下搜索即硼,如果一個對象不能達(dá)到GC Roots對象的時候树叽,說明它可以被回收了。這種算法比一種叫做引用計數(shù)法的垃圾標(biāo)記算法要好谦絮,因為它避免了當(dāng)兩個對象啊互相引用時無法被回收的現(xiàn)象题诵。

JVM中對于被標(biāo)記為垃圾的對象進(jìn)行回收時又分為了一下3種算法:

1.標(biāo)記清除算法洁仗,該算法是從根集合掃描整個空間,標(biāo)記存活的對象性锭,然后在掃描整個空間對沒有被標(biāo)記的對象進(jìn)行回收赠潦,這種算法在存活對象較多時比較高效,但會產(chǎn)生內(nèi)存碎片草冈。

2.復(fù)制算法她奥,該算法是從根集合掃描,并將存活的對象復(fù)制到新的空間怎棱,這種算法在存活對象少時比較高效哩俭。

3.標(biāo)記整理算法,標(biāo)記整理算法和標(biāo)記清除算法一樣都會掃描并標(biāo)記存活對象拳恋,在回收未標(biāo)記對象的同時會整理被標(biāo)記的對象凡资,解決了內(nèi)存碎片的問題。

JVM中谬运,不同的 內(nèi)存區(qū)域作用和性質(zhì)不一樣隙赁,使用的垃圾回收算法也不一樣,所以JVM中又定義了幾種不同的垃圾回收器(圖中連線代表兩個回收器可以同時使用):

1.Serial GC梆暖。從名字上看伞访,串行GC意味著是一種單線程的,所以它要求收集的時候所有的線程暫停轰驳。這對于高性能的應(yīng)用是不合理的厚掷,所以串行GC一般用于Client模式的JVM中。

2.ParNew GC级解。是在SerialGC的基礎(chǔ)上冒黑,增加了多線程機(jī)制。但是如果機(jī)器是單CPU的蠕趁,這種收集器是比SerialGC效率低的薛闪。

3.Parrallel Scavenge GC辛馆。這種收集器又叫吞吐量優(yōu)先收集器俺陋,而吞吐量=程序運(yùn)行時間/(JVM執(zhí)行回收的時間+程序運(yùn)行時間),假設(shè)程序運(yùn)行了100分鐘,JVM的垃圾回收占用1分鐘昙篙,那么吞吐量就是99%腊状。Parallel Scavenge GC由于可以提供比較不錯的吞吐量,所以被作為了server模式JVM的默認(rèn)配置苔可。

4.ParallelOld是老生代并行收集器的一種缴挖,使用了標(biāo)記整理算法,是JDK1.6中引進(jìn)的焚辅,在之前老生代只能使用串行回收收集器映屋。

5.Serial Old是老生代client模式下的默認(rèn)收集器苟鸯,單線程執(zhí)行,同時也作為CMS收集器失敗后的備用收集器棚点。

6.CMS又稱響應(yīng)時間優(yōu)先回收器早处,使用標(biāo)記清除算法。他的回收線程數(shù)為(CPU核心數(shù)+3)/4瘫析,所以當(dāng)CPU核心數(shù)為2時比較高效些砌梆。CMS分為4個過程:初始標(biāo)記、并發(fā)標(biāo)記贬循、重新標(biāo)記咸包、并發(fā)清除。

7.GarbageFirst(G1)杖虾。比較特殊的是G1回收器既可以回收Young Generation烂瘫,也可以回收Tenured Generation。它是在JDK6的某個版本中才引入的亏掀,性能比較高忱反,同時注意了吞吐量和響應(yīng)時間。

對于垃圾收集器的組合使用可以通過下表中的參數(shù)指定:

默認(rèn)的GC種類可以通過jvm.cfg或者通過jmap dump出heap來查看滤愕,一般我們通過jstat -gcutil [pid] 1000可以查看每秒gc的大體情況温算,或者可以在啟動參數(shù)中加入:-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:./gc.log來記錄GC日志

GC中有一種情況叫做Full GC间影,以下幾種情況會觸發(fā)Full GC:

1.Tenured Space空間不足以創(chuàng)建打的對象或者數(shù)組注竿,會執(zhí)行FullGC,并且當(dāng)FullGC之后空間如果還不夠魂贬,那么會OOM:java heap space巩割。

2.Permanet Generation的大小不足,存放了太多的類信息付燥,在非CMS情況下回觸發(fā)FullGC宣谈。如果之后空間還不夠,會OOM:PermGen space键科。

3.CMS GC時出現(xiàn)promotion failed和concurrent mode failure時闻丑,也會觸發(fā)FullGC。promotion failed是在進(jìn)行Minor GC時勋颖,survivor space放不下嗦嗡、對象只能放入舊生代,而此時舊生代也放不下造成的饭玲;concurrent mode failure是在執(zhí)行CMS GC的過程中同時有對象要放入舊生代侥祭,而此時舊生代空間不足造成的。

4.判斷MinorGC后,要晉升到TenuredSpace的對象大小大于TenuredSpace的大小矮冬,也會觸發(fā)FullGC谈宛。

可以看出,當(dāng)FullGC頻繁發(fā)生時胎署,一定是內(nèi)存出問題了入挣。

三、JVM的數(shù)據(jù)格式規(guī)范和Class文件

1.數(shù)據(jù)類型規(guī)范

依據(jù)馮諾依曼的計算機(jī)理論硝拧,計算機(jī)最后處理的都是二進(jìn)制的數(shù)径筏,而JVM是怎么把java文件最后轉(zhuǎn)化成了各個平臺都可以識別的二進(jìn)制呢?JVM自己定義了一個抽象的存儲數(shù)據(jù)單位障陶,叫做Word滋恬。一個字足夠大以持有byte、char抱究、short恢氯、int、float鼓寺、reference或者returnAdress的一個值勋拟,兩個字則足夠持有更大的類型long、double妈候。它通常是主機(jī)平臺一個指針的大小敢靡,如32位的平臺上,字是32位苦银。

同時JVM中定義了它所支持的基本數(shù)據(jù)類型啸胧,包括兩部分:數(shù)值類型和returnAddress類型。數(shù)值類型分為整形和浮點型幔虏。

整形:

byte

值是8位的有符號二進(jìn)制補(bǔ)碼整數(shù)

short

值是16位的有符號二進(jìn)制補(bǔ)碼整數(shù)

int

值是32位的有符號二進(jìn)制補(bǔ)碼整數(shù)

long

值是64位的有符號二進(jìn)制補(bǔ)碼整數(shù)

char

值是表示Unicode字符的16位無符號整數(shù)

浮點:

float

值是32位IEEE754浮點數(shù)

double

值是64位IEEE754浮點數(shù)

returnAddress類型的值是Java虛擬機(jī)指令的操作碼的指針纺念。

對比java的基本數(shù)據(jù)類型,jvm的規(guī)范中沒有boolean類型想括。這是因為jvm中堆boolean的操作是通過int類型來進(jìn)行處理的陷谱,而boolean數(shù)組則是通過byte數(shù)組來進(jìn)行處理。

至于String瑟蜈,我們知道它存儲在常量池中烟逊,但他不是基本數(shù)據(jù)類型,之所以可以存在常量池中踪栋,是因為這是JVM的一種規(guī)定焙格。如果查看String源碼图毕,我們就會發(fā)現(xiàn)夷都,String其實就是一個基于基本數(shù)據(jù)類型char的數(shù)組。如圖:

2.字節(jié)碼文件

通過字節(jié)碼文件的格式我們可以看出jvm是如何規(guī)范數(shù)據(jù)類型的。下面是ClassFile的結(jié)構(gòu):

關(guān)于各個字段的定義(參考自JVM Specification 和 博文:http://www.cnblogs.com/zhuYears/archive/2012/02/07/2340347.html)囤官,

Magic

魔數(shù)冬阳,魔數(shù)的唯一作用是確定這個文件是否為一個能被虛擬機(jī)所接受的Class文件。魔數(shù)值固定為0xCAFEBABE党饮,不會改變肝陪。

minor_version、major_version:

分別為Class文件的副版本和主版本刑顺。它們共同構(gòu)成了Class文件的格式版本號氯窍。不同版本的虛擬機(jī)實現(xiàn)支持的Class文件版本號也相應(yīng)不同,高版本號的虛擬機(jī)可以支持低版本的Class文件蹲堂,反之則不成立狼讨。

constant_pool_count:

常量池計數(shù)器,constant_pool_count的值等于constant_pool表中的成員數(shù)加1柒竞。

constant_pool[]:

常量池政供,constant_pool是一種表結(jié)構(gòu),它包含Class文件結(jié)構(gòu)及其子結(jié)構(gòu)中引用的所有字符串常量朽基、類或接口名布隔、字段名和其它常量。常量池不同于其他稼虎,索引從1開始到constant_pool_count?-1衅檀。

access_flags:

訪問標(biāo)志,access_flags是一種掩碼標(biāo)志霎俩,用于表示某個類或者接口的訪問權(quán)限及基礎(chǔ)屬性术吝。access_flags的取值范圍和相應(yīng)含義見下表:

this_class:

類索引,this_class的值必須是對constant_pool表中項目的一個有效索引值茸苇。constant_pool表在這個索引處的項必須為CONSTANT_Class_info類型常量排苍,表示這個Class文件所定義的類或接口。

super_class:

父類索引学密,對于類來說淘衙,super_class的值必須為0或者是對constant_pool表中項目的一個有效索引值。如果它的值不為0腻暮,那constant_pool表在這個索引處的項必須為CONSTANT_Class_info類型常量彤守,表示這個Class文件所定義的類的直接父類。當(dāng)然哭靖,如果某個類super_class的值是0具垫,那么它必定是java.lang.Object類,因為只有它是沒有父類的试幽。

interfaces_count:

接口計數(shù)器筝蚕,interfaces_count的值表示當(dāng)前類或接口的直接父接口數(shù)量。

interfaces[]:

接口表,interfaces[]數(shù)組中的每個成員的值必須是一個對constant_pool表中項目的一個有效索引值起宽,它的長度為interfaces_count洲胖。每個成員interfaces[i] 必須為CONSTANT_Class_info類型常量。

fields_count:

字段計數(shù)器坯沪,fields_count的值表示當(dāng)前Class文件fields[]數(shù)組的成員個數(shù)绿映。

fields[]:

字段表,fields[]數(shù)組中的每個成員都必須是一個fields_info結(jié)構(gòu)的數(shù)據(jù)項腐晾,用于表示當(dāng)前類或接口中某個字段的完整描述叉弦。

methods_count:

方法計數(shù)器,methods_count的值表示當(dāng)前Class文件methods[]數(shù)組的成員個數(shù)藻糖。

methods[]:

方法表卸奉,methods[]數(shù)組中的每個成員都必須是一個method_info結(jié)構(gòu)的數(shù)據(jù)項,用于表示當(dāng)前類或接口中某個方法的完整描述颖御。

attributes_count:

屬性計數(shù)器榄棵,attributes_count的值表示當(dāng)前Class文件attributes表的成員個數(shù)。

attributes[]:

屬性表潘拱,attributes表的每個項的值必須是attribute_info結(jié)構(gòu)疹鳄。

四、一個java類的實例分析

為了了解JVM的數(shù)據(jù)類型規(guī)范和內(nèi)存分配的大體情況芦岂,我新建了MemeryTest.java:

編譯為MemeryTest.class后瘪弓,通過WinHex查看該文件,對應(yīng)字節(jié)碼文件各個部分不同的定義禽最,我了解了下面16進(jìn)制數(shù)值的具體含義腺怯,盡管不清楚ClassLoader的具體實現(xiàn)邏輯,但是可以想象這樣一個嚴(yán)謹(jǐn)格式的文件給JVM對于內(nèi)存管理和執(zhí)行程序提供了多大的幫助川无。

運(yùn)行程序后呛占,我在windows資源管理器中找到對應(yīng)的進(jìn)程ID.

并且在控制臺通過jmap -heap 10016查看堆內(nèi)存的使用情況:

輸出結(jié)果中表示當(dāng)前java進(jìn)程啟動的JVM是通過4個線程進(jìn)行Parallel GC,堆的最小FreeRatio是40%懦趋,堆的最大FreeRatio是70%晾虑,堆的大小是4090M,新對象占用1.5M仅叫,Young Generation可以擴(kuò)展到最大是1363M帜篇, Tenured Generation的大小是254.5M,以及NewRadio和SurvivorRadio中诫咱,下面更是具體給出了目前Young Generation中1.5M的劃分情況笙隙,Eden占用1.0M,使用了5.4%坎缭,Space占了0.5M,使用了93%竟痰,To Space占了0.5M,使用了0%签钩。

下面我們通過jmap dump把heap的內(nèi)容打印打文件中:

使用Eclipse的MAT插件打開對應(yīng)的文件:

選擇第一項內(nèi)存泄露分析報告打開test.bin文件,展示出來的是MAT關(guān)于內(nèi)存可能泄露的分析凯亮。

從結(jié)果來看,有3個地方可能存在內(nèi)存泄露哄尔,他們占據(jù)了Heap的22.10%假消,13.78%,14.69%岭接,如果內(nèi)存泄露富拗,這里一般會有一個比值非常高的對象。打開第一個Probem Suspect鸣戴,結(jié)果如下:

ShallowHeap是對象本身占用的堆大小啃沪,不包含引用,RetainedHeap是對象所持有的Shallowheap的大小窄锅,包括自己ShallowHeap和可以引用的對象的ShallowHeap创千。垃圾回收的時候,如果一個對象不再引用后被回收入偷,那么他的RetainedHeap是能回收的內(nèi)存總和追驴。通過上圖可以看出程序中并沒有什么內(nèi)存泄露,可以放心了疏之。如果還有什么不太確定的對象殿雪,則可以通過多個時間點的HeapDumpFile來研究某個對象的變化情況。

大家可以點擊加入群:656039503【JAVA大牛學(xué)習(xí)交流】

里面有Java高級大牛直播講解知識點 走的就是高端路線

(如果你想跳槽換工作 但是技術(shù)又不夠 或者工作上遇到了

瓶頸 我這里有一個JAVA的免費(fèi)直播課程 講的是高端的知識點

基礎(chǔ)不好的誤入喲 只要你有1-5年的開發(fā)經(jīng)驗

可以加群找我要課堂鏈接 注意:是免費(fèi)的 沒有開發(fā)經(jīng)驗誤入

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锋爪,一起剝皮案震驚了整個濱河市丙曙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌其骄,老刑警劉巖亏镰,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拯爽,居然都是意外死亡拆挥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門某抓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纸兔,“玉大人,你說我怎么就攤上這事否副『嚎螅” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵备禀,是天一觀的道長洲拇。 經(jīng)常有香客問我奈揍,道長,這世上最難降的妖魔是什么赋续? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任男翰,我火速辦了婚禮,結(jié)果婚禮上纽乱,老公的妹妹穿的比我還像新娘蛾绎。我一直安慰自己,他們只是感情好鸦列,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布租冠。 她就那樣靜靜地躺著,像睡著了一般薯嗤。 火紅的嫁衣襯著肌膚如雪顽爹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天骆姐,我揣著相機(jī)與錄音镜粤,去河邊找鬼。 笑死玻褪,一個胖子當(dāng)著我的面吹牛繁仁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播归园,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼黄虱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了庸诱?” 一聲冷哼從身側(cè)響起捻浦,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎桥爽,沒想到半個月后朱灿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钠四,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年盗扒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缀去。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡侣灶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缕碎,到底是詐尸還是另有隱情褥影,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布咏雌,位于F島的核電站凡怎,受9級特大地震影響校焦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜统倒,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一寨典、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧房匆,春花似錦耸成、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捆昏。三九已至赚楚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骗卜,已是汗流浹背宠页。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留寇仓,地道東北人举户。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像遍烦,于是被迫代替她去往敵國和親俭嘁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • JVM內(nèi)存模型Java虛擬機(jī)(Java Virtual Machine=JVM)的內(nèi)存空間分為五個部分服猪,分別是: ...
    光劍書架上的書閱讀 2,483評論 2 26
  • Java 虛擬機(jī)有自己完善的硬件架構(gòu), 如處理器供填、堆棧、寄存器等罢猪,還具有相應(yīng)的指令系統(tǒng)近她。JVM 屏蔽了與具體操作系...
    尹小凱閱讀 1,682評論 0 10
  • 1.一些概念 1.1.數(shù)據(jù)類型 Java虛擬機(jī)中,數(shù)據(jù)類型可以分為兩類:基本類型和引用類型膳帕≌成樱基本類型的變量保存原始...
    落落落落大大方方閱讀 4,520評論 4 86
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理,因此不免有一些不準(zhǔn)確的地方危彩,同時不同JDK版本的...
    高廣超閱讀 15,545評論 3 83
  • 笑笑: 因為高血壓攒磨,羊水太少,媽媽怕你出危險汤徽,于2017年6月19日咧纠,提前十天做了剖腹產(chǎn)手術(shù),你降臨到人間...
    雪中萍閱讀 504評論 4 4