一、jvm內(nèi)存模型
下面說一下大概的一個(gè)流程:
- class文件會(huì)被類裝載子系統(tǒng)裝載。
- 裝載到內(nèi)存中也就是jvm運(yùn)行時(shí)的數(shù)據(jù)區(qū)擅腰。
- 當(dāng)我們運(yùn)行一個(gè)方法的時(shí)候會(huì)創(chuàng)建一個(gè)對(duì)應(yīng)的棧楨,
棧楨包含了操作數(shù)棧翁潘、局部變量表趁冈、動(dòng)態(tài)鏈接、方法出口等部分拜马,每執(zhí)行一個(gè)操作渗勘,都會(huì)在程序計(jì)數(shù)器中,記錄程序下一步要執(zhí)行的指針地址俩莽。 - 當(dāng)方法1執(zhí)行完后會(huì)從方法出口旺坠,進(jìn)入到方法二的棧楨中。
- 當(dāng)聲明一個(gè)方法的時(shí)候扮超,會(huì)存到到堆區(qū)域取刃。
- 方法區(qū)是線程共享的里面存的是類的所有字段和方法的字節(jié)碼如Math類
- 執(zhí)行引擎讀取運(yùn)行時(shí)數(shù)據(jù)區(qū)的字節(jié)碼逐個(gè)執(zhí)行
二、jvm內(nèi)存結(jié)構(gòu)
本地方法棧(線程私有):本地方法棧是與虛擬機(jī)棧發(fā)揮的作用十分相似,區(qū)別是虛擬機(jī)棧執(zhí)行的是Java方法(也就是字節(jié)碼)服務(wù)出刷,而本地方法棧則為虛擬機(jī)使用到的native方法服務(wù)璧疗,可能底層調(diào)用的c或者c++,我們打開jdk安裝目錄可以看到也有很多用c編寫的文件,可能就是native方法所調(diào)用的c代碼
程序計(jì)數(shù)器(線程私有):就是一個(gè)指針馁龟,指向方法區(qū)中的方法字節(jié)碼崩侠,可以認(rèn)作為當(dāng)前線程的行號(hào)指示器(用來存儲(chǔ)指向下一條指令的地址,也即將要執(zhí)行的指令代碼),由執(zhí)行引擎讀取下一條指令坷檩,是一個(gè)非常小的內(nèi)存空間却音,幾乎可以忽略不記改抡。
方法區(qū)(線程共享):類的所有字段和方法字節(jié)碼,以及一些特殊方法如構(gòu)造函數(shù)系瓢,接口代碼也在此定義雀摘。簡單說,所有定義的方法的信息都保存在該區(qū)域八拱,靜態(tài)變量+常量+類信息(構(gòu)造方法/接口定義)+運(yùn)行時(shí)常量池都存在方法區(qū)中阵赠,雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是它卻有一個(gè)別名叫做 Non-Heap(非堆)肌稻,目的應(yīng)該是與 Java 堆區(qū)分開來
虛擬機(jī)棧(線程私有):棧的算法是先進(jìn)后出的清蚀, Java線程執(zhí)行方法的內(nèi)存模型,一個(gè)線程對(duì)應(yīng)一個(gè)棧爹谭,每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(用于存儲(chǔ)局部變量表枷邪,操作數(shù)棧,動(dòng)態(tài)鏈接诺凡,方法出口等信息)东揣。每一個(gè)方法被調(diào)用的過程就對(duì)應(yīng)一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過程,不存在垃圾回收問題腹泌,只要線程一結(jié)束該棧就釋放嘶卧,生命周期和線程一致
三、jvm堆結(jié)構(gòu)
首先介紹一下堆凉袱,堆是Java 虛擬機(jī)所管理的內(nèi)存中最大的一塊芥吟,并且是線程共享的,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建专甩,用于存放對(duì)象實(shí)例钟鸵,幾乎所有的對(duì)象(包括常量池)都在堆上分配內(nèi)存,當(dāng)對(duì)象無法在該空間申請(qǐng)到內(nèi)存時(shí)就會(huì)拋出內(nèi)存溢出異常涤躲,堆分為新生代(1/3堆空間)棺耍、老年代(2/3 堆空間)、元空間种樱,元空間在就是jdk1.8以前的永久代蒙袍,是方法區(qū)的實(shí)現(xiàn),直接存在內(nèi)存中缸托,下面分別對(duì)堆的幾個(gè)區(qū)域進(jìn)行介紹
新生代:
分為兩部分伊甸區(qū)和幸存者區(qū)左敌,所有的類都是在伊甸區(qū)被new出來的,幸存區(qū)有兩個(gè)0區(qū)和1區(qū)當(dāng)伊甸區(qū)空間滿了垃圾回收器會(huì)對(duì)伊甸區(qū)進(jìn)行Minor GC俐镐,將伊甸區(qū)中不再被其他對(duì)象所引用的對(duì)象進(jìn)行銷毀矫限,然后將伊甸園區(qū)的剩余對(duì)象移到幸存0區(qū),若幸存0區(qū)也滿了,會(huì)再對(duì)該去進(jìn)行Minor GC叼风,然后移到1區(qū)取董,新生代中的eden->from->to 每熬過一次Minor GC,年齡會(huì)加1无宿,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲)茵汰,就會(huì)晉升為老年代。老年代:
新生代進(jìn)行多次Minor GC仍然存活的對(duì)象會(huì)移動(dòng)到老年區(qū)孽鸡,若老年區(qū)也滿了蹂午,會(huì)產(chǎn)生FullGC,進(jìn)行老年區(qū)的內(nèi)存清理彬碱。若老年區(qū)執(zhí)行了Full GC之后依然無法進(jìn)行對(duì)象保存豆胸,就會(huì)拋出內(nèi)存溢出異常。
-Xmx和-Xms參數(shù)表示最大堆和最小堆-
元數(shù)據(jù)區(qū):
元數(shù)據(jù)區(qū)就是jdk1.8以前的永久代巷疼,是對(duì)jvm規(guī)范中方法區(qū)的實(shí)現(xiàn)晚胡,使用的是本地物理內(nèi)存,而1.8以前的永久代在虛擬機(jī)中嚼沿,永久代在邏輯結(jié)構(gòu)上屬于堆估盘,但是物理上不屬于堆,堆大小=新生代+老年代骡尽,元數(shù)據(jù)區(qū)也可能發(fā)生內(nèi)存溢出的異常遣妥。
四、類加載機(jī)制
- 全盤負(fù)責(zé)委托機(jī)制
當(dāng)一個(gè)ClassLoader加載一個(gè)類時(shí)爆阶,除非顯示的使用另一個(gè)ClassLoader燥透,該類所依賴和引用的類也由這個(gè)ClassLoader載入 - 雙親委派機(jī)制
指先委托父類加載器尋找目標(biāo)類,在找不到的情況下在自己的路徑中查找并載入目標(biāo)類
優(yōu)勢(shì):- 沙箱安全機(jī)制:自己寫的String.class類不會(huì)被加載辨图,這樣可以防止核心API庫被隨意篡改
- 避免類的被重復(fù)加載:當(dāng)父親已經(jīng)加載了該類時(shí),子加載器就沒必要再加載一次
五肢藐、GC相關(guān)
5.1 垃圾收集算法
- 引用計(jì)數(shù)法
給對(duì)象中添加一個(gè)引用計(jì)數(shù)器故河,沒當(dāng)有一個(gè)地方引用它,計(jì)數(shù)器就加1吆豹;當(dāng)引用失效鱼的,計(jì)數(shù)器就減1;任何時(shí)候計(jì)數(shù)器為0的對(duì)象就認(rèn)為可以被回收痘煤。(這個(gè)方法實(shí)現(xiàn)簡單凑阶,效率高,但是目前主流虛擬機(jī)沒有選擇這個(gè)算法管理內(nèi)存衷快,主要原因是很難解決對(duì)象之間相互循環(huán)引用的問題宙橱。) - 可達(dá)性分析算法
以“GC Roots”的對(duì)象作為起點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,節(jié)點(diǎn)所走過的路徑稱為引用鏈师郑,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連的話环葵,則證明此對(duì)象時(shí)不可能用的 - finalize()方法最終判定對(duì)象是否存活
即使在可達(dá)性分析算法中不可達(dá)的對(duì)象,需要經(jīng)歷再次標(biāo)記過程才真正宣告一個(gè)對(duì)象死亡
5.2 垃圾清除算法
-
標(biāo)記清除算法
算法分為“標(biāo)記”和“清除”階段:首先標(biāo)記出所有需要回收的對(duì)象宝冕,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象张遭。它是最基礎(chǔ)的收集算法,效率也很高地梨,但是會(huì)帶來兩個(gè)明顯的問題:- 效率問題(會(huì)遍歷內(nèi)存)
-
空間問題(標(biāo)記清除后會(huì)產(chǎn)生大量不連續(xù)的碎片)
-
復(fù)制算法
為了解決效率問題菊卷,“復(fù)制”收集算法出現(xiàn)了。它可以將內(nèi)存分為大小相同的兩塊宝剖,每次使用其中的一塊洁闰。當(dāng)這一塊的內(nèi)存使用完后,就將還存活的對(duì)象復(fù)制到另一塊去诈闺,然后再把使用的空間一次清理掉渴庆。這樣就使每次的內(nèi)存回收都是對(duì)內(nèi)存區(qū)間的一半進(jìn)行回收。
標(biāo)記整理算法
為了解決效率問題雅镊,“復(fù)制”收集算法出現(xiàn)了襟雷。它可以將內(nèi)存分為大小相同的兩塊,每次使用其中的一塊仁烹。當(dāng)這一塊的內(nèi)存使用完后耸弄,就將還存活的對(duì)象復(fù)制到另一塊去,然后再把使用的空間一次清理掉卓缰。這樣就使每次的內(nèi)存回收都是對(duì)內(nèi)存區(qū)間的一半進(jìn)行回收计呈。
- 分代收集算法
當(dāng)前虛擬機(jī)的垃圾收集都采用分代收集算法,這種算法沒有什么新的思想征唬,只是根據(jù)對(duì)象存活周期的不同將內(nèi)存分為幾塊捌显。一般將java堆分為新生代和老年代,這樣我們就可以根據(jù)各個(gè)年代的特點(diǎn)選擇合適的垃圾收集算法总寒。
比如在新生代中扶歪,每次收集都會(huì)有大量對(duì)象死去,所以可以選擇復(fù)制算法摄闸,只需要付出少量對(duì)象的復(fù)制成本就可以完成每次垃圾收集善镰。而老年代的對(duì)象存活幾率是比較高的,而且沒有額外的空間對(duì)它進(jìn)行分配擔(dān)保年枕,所以我們必須選擇“標(biāo)記-清除”或“標(biāo)記-整理”算法進(jìn)行垃圾收集炫欺。
- 新生代適合復(fù)制算法
- 老年代適合標(biāo)記清除算法和標(biāo)記整理算法
5.3 垃圾收集器
Serial收集器
最早的收集器,根據(jù)名字就可以看出這是一款單線程收集器ParNew收集器
是Serial收集器的多線程版本Parallel Scavenge收集器
類似于ParNew收集器CMS收集器
是一款并發(fā)收集器G1收集器
G1 (Garbage-First)是一款面向服務(wù)器的垃圾收集器,主要針對(duì)配備多顆處理器及大容量內(nèi)存的機(jī)器. 以極高概率滿足GC停頓時(shí)間要求的同時(shí),還具備高吞吐量性能特征熏兄,可以設(shè)置吞吐量品洛,GC會(huì)盡量滿足設(shè)置的吞吐量
六树姨、JVM調(diào)優(yōu)相關(guān)
JVM調(diào)優(yōu)指標(biāo)
- 停頓時(shí)間:垃圾收集器做垃圾回收中斷應(yīng)用執(zhí)行的時(shí)間
- 吞吐量: 垃圾收集的時(shí)間和總時(shí)間的占比:1/(1+n),吞吐量為1-1/(1+n)
調(diào)優(yōu)步驟
- 打印GC日志
- 分析日志得到關(guān)鍵性指標(biāo)
- 分析GC原因毫别,調(diào)優(yōu)JVM參數(shù)
調(diào)優(yōu)命令
- jps:查看jvm進(jìn)程
- jstat:
- jstat -gc 28485 垃圾回收統(tǒng)計(jì)
- jstat -class 28485
- jinfo:查看內(nèi)存信息
- jinfo -flags 28485 (jvm運(yùn)行時(shí)的參數(shù))
- jmap:實(shí)例個(gè)數(shù)即占用內(nèi)存大小
- jmap -histo 28485 查看運(yùn)行時(shí)內(nèi)存相關(guān)信息
- jmap -heap 28485 查看堆的概況
- jstack:死鎖排查
- jvisualvm JDK自帶的可視化工具