談到JVM轧坎,對于很多干了三四年的程序員來說,還是很模糊竿奏。我待了好幾個公司袄简,問過很多干了3-5年Java開發(fā)的同事,大部分人其實對于JVM理解都不深(當然對于我自己來說泛啸,也是一樣)。JVM相當于一個武林高手的內(nèi)功秃症,內(nèi)功強大才是真的強候址,所以我用本文整理出來JVM的重點知識。
JVM總體分為四大塊种柑,每一塊都有大量的細節(jié)內(nèi)容說岗仑,這里只做主干梳理
一,類的加載機制
1.什么是類的加載
類的加載指的是將類的.class文件中的二進制數(shù)據(jù)讀入到內(nèi)存中聚请,將其放在運行時數(shù)據(jù)區(qū)的方法區(qū)內(nèi)荠雕,然后在堆區(qū)創(chuàng)建一個java.lang.Class對象,用來封裝類在方法區(qū)的數(shù)據(jù)結(jié)構(gòu)驶赏。類的加載最終是位于堆區(qū)中的Class對象炸卑,Class對象封裝了類在方法區(qū)的數(shù)據(jù)結(jié)構(gòu),并且向程序員提供了訪問方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口
2.類的生命周期
類的生命周期包括這幾個部分煤傍,加載盖文、連接、初始化蚯姆、使用和卸載五续。前三步是類的加載過程洒敏,如下圖
加載:查找并加載類的二進制數(shù)據(jù)凶伙,在Java堆中也創(chuàng)建一個java.lang.Class類的對象
連接:連接又包含三塊內(nèi)容:驗證、準備它碎、解析函荣。1)驗證,文件格式链韭、元數(shù)據(jù)偏竟、字節(jié)碼、符號引用驗證敞峭;2)準備踊谋,為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認值旋讹;3)解析殖蚕,把類中的符號引用轉(zhuǎn)換為直接引用
初始化:為類的靜態(tài)變量賦予正確的初始值
使用:new出對象在程序中使用
卸載:執(zhí)行垃圾回收
3.類加載器與雙親委派模型
從虛擬機角度看,只存在2種類加載器
一種是啟動類加載器(Bootstrap ClassLoader)沉迹,使用C++實現(xiàn)的睦疫,是虛擬機本身的一種。
一種是其他的類加載器鞭呕,使用Java實現(xiàn)蛤育,獨立于虛擬機,繼承于java.lang.ClassLoader葫松。
從Java開發(fā)者的角度看瓦糕,類加載器可以進一步劃分,一般情況分為3種
第一種:啟動類加載器(Bootstrap ClassLoader) 腋么,這個類加載器負責將存放在<JAVA_HOME>\lib目錄下的咕娄,或被-Xbootclasspath參數(shù)所制定的路徑種的,并且是被虛擬機識別的類庫加載到虛擬機內(nèi)存中啟動類加載器無法被Java程序直接引用珊擂,用戶在編寫自定義類加載器時圣勒,如果需要加載請求委派到引導(dǎo)類加載器,可直接使用null代替即可摧扇。
第二種:擴展類加載器(Extension ClassLoader) 圣贸,該加載器由sun.misc.Lanucher$ExtClassLoader實現(xiàn),負責加載<JAVA_HOME>\lib\ext目錄中扳剿,或者被java.ext.dirs系統(tǒng)變量指定路徑中的類庫藏鹊,開發(fā)者可以直接使用擴展類加載器
第三種:應(yīng)用程序類加載器<Application ClassLoader>踱蠢, 該類是ClassLoader中g(shù)etSystemClassLoader()方法返回值障本,所以一般稱為系統(tǒng)類加載器。負責加載用戶類路徑(ClassPath)上所指定的類庫橙困,開發(fā)者可以直接使用這個類加載器,如果應(yīng)用程序沒有自定義類加載器耕餐,一般情況下是默認的類加載器
為什么采用雙親委派模型凡傅,如圖
——使得Java類隨著類加載器不同而具備帶優(yōu)先級的層次關(guān)系槽华,如java.lang.Object(位于rt.jar內(nèi)),無論那個類加載器要加載該類趟妥,最終都委派給頂層引導(dǎo)類加載器猫态,因此Object類在程序的各種類加載環(huán)境中都是同一個類。
——如果沒有雙親委派披摄,用戶自定義重名的類亲雪,將會使得系統(tǒng)帶有多個同名的類,使得基礎(chǔ)的Java類型體系混亂雙親委派模型對于保證Java程序的穩(wěn)定運行十分重要疚膊,它實現(xiàn)卻很簡單首先檢查是否被加載過义辕,若沒有加載則調(diào)用父類加載器的loadClass方法,若父類加載器為空寓盗,則默認使用引導(dǎo)類加載器作為父類加載器灌砖,如果加載失敗,則調(diào)用自身的findClass()方法加載
——破壞雙親委派情形:使用JNDI服務(wù)傀蚌、代碼模塊熱部署
二周崭,JVM內(nèi)存結(jié)構(gòu)
1.內(nèi)存結(jié)構(gòu)組成
Java堆(Heap):是Java虛擬機所管理的內(nèi)存中最大的一塊美澳。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域销部,在虛擬機啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實例制跟,幾乎所有的對象實例都在這里分配內(nèi)存舅桩。
方法區(qū)(Method Area):方法區(qū)(Method Area)與Java堆一樣,是各個線程共享的內(nèi)存區(qū)域雨膨,它用于存儲已被虛擬機加載的類信息擂涛、常量、靜態(tài)變量聊记、即時編譯器編譯后的代碼等數(shù)據(jù)撒妈。
程序計數(shù)器(Program Counter Register):程序計數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間恢暖,它的作用可以看做是當前線程所執(zhí)行的字節(jié)碼的行號指示器。
JVM棧(JVM Stacks):與程序計數(shù)器一樣狰右,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的杰捂,它的生命周期與線程相同。虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法被執(zhí)行的時候都會同時創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表棋蚌、操作棧嫁佳、動態(tài)鏈接、方法出口等信息谷暮。每一個方法被調(diào)用直至執(zhí)行完成的過程蒿往,就對應(yīng)著一個棧幀在虛擬機棧中從入棧到出棧的過程。
本地方法棧(Native Method Stacks):本地方法棧(Native Method Stacks)與虛擬機棧所發(fā)揮的作用是非常相似的湿弦,其區(qū)別不過是虛擬機棧為虛擬機執(zhí)行Java方法(也就是字節(jié)碼)服務(wù)瓤漏,而本地方法棧則是為虛擬機使用到的Native方法服務(wù)。
2.對象分配規(guī)則
對象優(yōu)先分配在Eden區(qū)赌蔑,如果Eden區(qū)沒有足夠的空間時,虛擬機執(zhí)行一次Minor?GC竟秫。
大對象直接進入老年代(大對象是指需要大量連續(xù)內(nèi)存空間的對象)娃惯。這樣做的目的是避免在Eden區(qū)和兩個Survivor區(qū)之間發(fā)生大量的內(nèi)存拷貝(新生代采用復(fù)制算法收集內(nèi)存)。
長期存活的對象進入老年代肥败。虛擬機為每個對象定義了一個年齡計數(shù)器趾浅,如果對象經(jīng)過了1次Minor?GC那么對象會進入Survivor區(qū),之后每經(jīng)過一次Minor?GC那么對象的年齡加1馒稍,知道達到閥值對象進入老年區(qū)皿哨。
動態(tài)判斷對象的年齡。如果Survivor區(qū)中相同年齡的所有對象大小的總和大于Survivor空間的一半纽谒,年齡大于或等于該年齡的對象可以直接進入老年代证膨。
空間分配擔保。每次進行Minor?GC時鼓黔,JVM會計算Survivor區(qū)移至老年區(qū)的對象的平均大小央勒,如果這個值大于老年區(qū)的剩余值大小則進行一次Full?GC,如果小于檢查HandlePromotionFailure設(shè)置澳化,如果true則只進行Monitor?GC,如果false則進行Full?GC崔步。
三,GC算法 垃圾回收
1.對象存活判斷
判斷對象是否存活一般有兩種方式:
引用計數(shù):每個對象有一個引用計數(shù)屬性缎谷,新增一個引用時計數(shù)加1井濒,引用釋放時計數(shù)減1,計數(shù)為0時可以回收。此方法雖然簡單瑞你,但是無法解決對象相互循環(huán)引用的問題酪惭。
可達性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索走過的路徑稱為引用鏈捏悬。當一個對象到GC Roots沒有任何引用鏈相連時撞蚕,則證明此對象是不可用的,為不可達對象过牙。
2.GC算法
GC最基礎(chǔ)的算法有三種:標記 -清除算法甥厦、復(fù)制算法、標記-壓縮算法
我們常用的垃圾回收器一般都采用分代收集算法寇钉。
標記 -清除算法:“標記-清除”(Mark-Sweep)算法刀疙,如它的名字一樣,算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象扫倡,在標記完成后統(tǒng)一回收掉所有被標記的對象谦秧。
復(fù)制算法:“復(fù)制”(Copying)的收集算法,它將可用內(nèi)存按容量劃分為大小相等的兩塊撵溃,每次只使用其中的一塊疚鲤。當這一塊的內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面缘挑,然后再把已使用過的內(nèi)存空間一次清理掉集歇。
標記-壓縮算法:標記過程仍然與“標記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對象進行清理语淘,而是讓所有存活的對象都向一端移動诲宇,然后直接清理掉端邊界以外的內(nèi)存分代收集算法
分代收集(Generational Collection)算法:把Java堆分為新生代和老年代,這樣就可以根據(jù)各個年代的特點采用最適當?shù)氖占惴ā?/p>
3.垃圾回收器
Serial收集器:串行收集器是最古老惶翻,最穩(wěn)定以及效率高的收集器姑蓝,可能會產(chǎn)生較長的停頓,只使用一個線程去回收吕粗。
ParNew收集器:ParNew收集器其實就是Serial收集器的多線程版本纺荧。
Parallel收集器:Parallel Scavenge收集器類似ParNew收集器,Parallel收集器更關(guān)注系統(tǒng)的吞吐量颅筋。
Parallel Old 收集器:Parallel Old是Parallel Scavenge收集器的老年代版本虐秋,使用多線程和“標記-清理”算法
CMS收集器:CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。
G1收集器:G1 (Garbage-First)是一款面向服務(wù)器的垃圾收集器,主要針對配備多顆處理器及大容量內(nèi)存的機器. 以極高概率滿足GC停頓時間要求的同時,還具備高吞吐量性能特征
四垃沦,GC調(diào)優(yōu)
1.調(diào)優(yōu)工具
常用調(diào)優(yōu)工具分為兩類
JDK自帶監(jiān)控工具:Jconsole 和 Jvisualvm
第三方:MAT(Memory Analyzer Tool)、GChisto
Jconsole用押,Java Monitoring and Management Console?是從java5開始肢簿,在JDK中自帶的java監(jiān)控和管理控制臺,用于對JVM中內(nèi)存,線程和類等的監(jiān)控?
?Jvisualvm池充,jdk自帶全能工具桩引,可以分析內(nèi)存快照、線程快照收夸;監(jiān)控內(nèi)存變化坑匠、GC變化等。
MAT卧惜,Memory Analyzer Tool厘灼,一個基于Eclipse的內(nèi)存分析工具,是一個快速咽瓷、功能豐富的Java heap分析工具设凹,它可以幫助我們查找內(nèi)存泄漏和減少內(nèi)存消耗
GChisto,一款專業(yè)分析gc日志的工具