JVM 知識(shí)點(diǎn)全面梳理!
上一篇:
絕了!一個(gè)妹子 rm -rf 把公司整個(gè)數(shù)據(jù)庫(kù)刪沒(méi)了...
java中就虛擬機(jī)是其他語(yǔ)言編寫的(C語(yǔ)言+匯編語(yǔ)言,因此,JVM最常出現(xiàn)的攻擊就是buffer overflow),如javac命令等,而java api是java寫的,大多開(kāi)源在openjdk,jdk中有一個(gè)src.jar,就是JDk的源碼,本文是JVM基礎(chǔ)知識(shí)的一個(gè)匯總,方便查閱,內(nèi)容較多。
1、JVM的內(nèi)存模型
JDK7內(nèi)存模型(圖來(lái)自于網(wǎng)絡(luò)):???????????????????????

JDK8內(nèi)存模型(圖來(lái)自于網(wǎng)絡(luò)):

JVM內(nèi)存模型說(shuō)明:
?JDK7中內(nèi)存模型包括:方法區(qū),堆區(qū),棧區(qū),本地方法棧,計(jì)數(shù)器,分為兩個(gè)類型的區(qū)域,一種是線程私有的,一種是線程共享的,其中,方法區(qū)和堆區(qū)是線程共享的,其他都是線程私有的,堆區(qū)是被GC的主要區(qū)域,方法區(qū)有部分廢棄的常量可以被GC,下面詳細(xì)的說(shuō)明:
?(1)計(jì)數(shù)器,線程私有,當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器,標(biāo)記當(dāng)前執(zhí)行到了哪一行指令
(2)虛擬機(jī)棧,線程私有,生命周期和線程相同,存局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口(即方法返回地址)信息等
其中,局部變量表存編譯器可知的各種數(shù)據(jù)類型,包括boolean、char、byte、short、int、float、double,及對(duì)象的引用
(3)本地方法棧,和虛擬機(jī)棧所發(fā)揮的作用非常相似,區(qū)別是,虛擬機(jī)棧為虛擬機(jī)執(zhí)行java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的native方法服務(wù),在?HotSpot?虛擬機(jī)中和?Java?虛擬機(jī)棧合二為一。本地方法被執(zhí)行的時(shí)候,在本地方法棧也會(huì)創(chuàng)建一個(gè)棧幀,用于存放該本地方法的?局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、出口信息
?(4)堆,java虛擬機(jī)所管理的內(nèi)存中最大的一塊,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)?象實(shí)例以及數(shù)組都在這里分配內(nèi)存
?(5)方法區(qū),用于存儲(chǔ)已被虛擬機(jī)加載的類的信息(字段、方法)、常量、靜態(tài)變量、即時(shí)編輯器編譯后的代碼等數(shù)據(jù),運(yùn)行時(shí)常量池:屬于方法區(qū),包含字面量(字符串、final常量)、符號(hào)引用
其中,方法區(qū)的詳細(xì)說(shuō)明:
方法區(qū),Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是它卻有一個(gè)別名叫?做?Non-Heap(非堆),目的應(yīng)該是與?Java?堆區(qū)分開(kāi)來(lái),方法區(qū)也被稱為永久代,但方法區(qū)和永久代并不完全等同,只是為了便于管理,這樣,HotSpot虛擬機(jī)的垃圾收集器就可以像管理java堆一樣管理這部分內(nèi)存,方法區(qū)可被GC回收的那部分內(nèi)存是廢棄的常量,但這樣管理并不好,會(huì)容易產(chǎn)生內(nèi)存溢出,所以在JDK8中,移除了方法區(qū),將方法區(qū)中的常量池移至堆中(字符串池和類的靜態(tài)變量放入java堆中),其他移到直接內(nèi)存中,命名為“元空間”,解決了永久代會(huì)出現(xiàn)內(nèi)存溢出的問(wèn)題,但是要注意,需要設(shè)置元空間的最大大小(-XX:MaxMetaspaceSize設(shè)置),否則,如果不指定大小的話,隨著更多類的創(chuàng)建,虛擬機(jī)會(huì)耗盡所有可用的系統(tǒng)內(nèi)存
?(6)直接內(nèi)存,不屬于JVM運(yùn)行時(shí)數(shù)據(jù)區(qū),JVM的NIO方法可以分配堆外內(nèi)存如使用?DirectByteBuffer
2、堆棧的異常總結(jié)
?? (1)堆內(nèi)存溢出OutOfMemoryError:java heap space
產(chǎn)生原因:java堆用于存儲(chǔ)對(duì)象實(shí)例,只要不斷的創(chuàng)建對(duì)象,并保證GC roots到對(duì)象間有可達(dá)路徑避免這些對(duì)象的GC,那么,當(dāng)對(duì)象數(shù)量到達(dá)堆的最大容量限制后就會(huì)產(chǎn)生OOM
解決辦法:
通過(guò)參數(shù)?-XX:HeapDumpOnOutOfMemoryError?可以讓虛擬機(jī)在內(nèi)存溢出異常時(shí)Dump當(dāng)前內(nèi)存堆轉(zhuǎn)儲(chǔ)快照
通過(guò)內(nèi)存映像分析工具(如:Eclipse Memory Analyzer)對(duì)Dump出的堆轉(zhuǎn)儲(chǔ)快照分析,判斷是內(nèi)存泄露還是內(nèi)存溢出
如果是內(nèi)存泄露:通過(guò)工具查看泄露對(duì)象的類型信息和它們到?GC Roots?的引用鏈信息,分析GC收集器無(wú)法自動(dòng)回收它們的原因,定位內(nèi)存泄露的代碼位置
如果是內(nèi)存溢出:檢查堆參數(shù)?-Xms和-Xmx,看是否可調(diào)大;代碼上檢查某些對(duì)象生命周期過(guò)長(zhǎng),持有時(shí)間過(guò)長(zhǎng)的情況,嘗試減少程序運(yùn)行期間內(nèi)存消耗
(2)除程序計(jì)數(shù)器外,JVM其他幾個(gè)運(yùn)行時(shí)區(qū)域都可能發(fā)生OutOfMemoryError異常
(3)棧的兩種異常
1.?StackOverFlowError異常:線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的最大深度
一個(gè)會(huì)發(fā)生stackOverFlow的場(chǎng)景:無(wú)限遞歸,沒(méi)有出口
2.OutOfMemoryError異常:虛擬機(jī)擴(kuò)展棧時(shí)無(wú)法申請(qǐng)足夠的內(nèi)存空間
一個(gè)會(huì)發(fā)生OutOfMemory的場(chǎng)景:list,無(wú)限添加元素
解決虛擬機(jī)棧兩種異常的辦法:
1.檢查代碼中是否有死遞歸
2.配置?-Xss?增大每個(gè)線程的棧內(nèi)存容量,但會(huì)減少工作線程數(shù),需要權(quán)衡
3、常用的JVM參數(shù)設(shè)置
(1)-Xss,設(shè)置棧的大小,不熟悉最好使用默認(rèn)值,當(dāng)棧中存儲(chǔ)數(shù)據(jù)比較多時(shí),需要適當(dāng)調(diào)大這個(gè)值,否則會(huì)出現(xiàn)java.lang.StackOverflowError異常
(2)?堆內(nèi)存設(shè)置:
①?-Xms,初始堆大小,Server端JVM最好將-Xms和-Xmx設(shè)為相同值,開(kāi)發(fā)測(cè)試機(jī)JVM可以保留默認(rèn)值
②?-Xmx,最大堆大小,默認(rèn)為物理內(nèi)存的1/4,最佳設(shè)值應(yīng)該視物理內(nèi)存大小及計(jì)算機(jī)內(nèi)其他內(nèi)存開(kāi)銷而定
? 默認(rèn)空余堆內(nèi)存小于?40%時(shí),JVM就會(huì)增大堆直到-Xmx的最大限制;空余堆內(nèi)存大于70%時(shí),JVM會(huì)減少堆直到-Xms的最小限制。因此服務(wù)器一般設(shè)置-Xms、?-Xmx相等以避 免在每次GC?后調(diào)整堆的大小。
③?-Xmn:年輕代大小(young區(qū)大小),通常為?Xmx?的?1/3?或?1/4,不熟悉最好保留默認(rèn)值
④?-XX:SurvivorRatio,設(shè)置年輕代Eden和單個(gè)S區(qū)的比例,默認(rèn)為8,即Eden為8/10,兩個(gè)survivor分別為1/10,其中一個(gè)survivor閑置,用于復(fù)制,所以年輕代實(shí)際可用的內(nèi)存 大小為-Xmn設(shè)置值的9/10,Eden設(shè)置的太大的話,會(huì)導(dǎo)致GC變慢,并且沒(méi)有足夠的survivor幸存者空間,會(huì)導(dǎo)致GC直接到達(dá)老年代,老年代滿的更快,會(huì)更早觸發(fā)FullfGC, Eden設(shè)置的過(guò)小,則MinorGC頻繁,會(huì)影響線上程序運(yùn)行,因?yàn)?/span>GC會(huì)導(dǎo)致應(yīng)用程序暫停
⑤?-XX:MaxTenuringThreshold,設(shè)置年輕代中回收區(qū)對(duì)象的年齡,默認(rèn)15,可通過(guò)命令指定,如果設(shè)置為0,表示Eden回收時(shí),不經(jīng)過(guò)Survivor,直接到達(dá)老年代
? (3)?持久代設(shè)置:
①?-XX:PermSize,方法區(qū)(永久代,或稱非堆區(qū))初始分配的內(nèi)存大小,其全稱為permanent size(持久化內(nèi)存)
②?-XX:MaxPermSize=64m,永久代的最大大小,超過(guò)這個(gè)值,將會(huì)拋出OutOfMemoryError:PermGen
? 在配置之前一定要慎重的考慮一下自身軟件所需要的非堆區(qū)內(nèi)存大小,因?yàn)槌志么鷥?nèi)存是不會(huì)被java垃圾回收機(jī)制進(jìn)行處理的地方。
?最大堆內(nèi)存與最大非堆內(nèi)存的和絕對(duì)不能夠超出操作系統(tǒng)的可用內(nèi)存。
? ?(4)?元空間的設(shè)置:
JDK 1.8?的時(shí)候,方法區(qū)(HotSpot?的永久代)被徹底移除了(JDK1.7?就已經(jīng)開(kāi)始?了),取而代之是元空間,元空間使用的是直接內(nèi)存。配置如下:
-XX:MetaspaceSize=N //設(shè)置?Metaspace?的初始(和最小大小)
-XX:MaxMetaspaceSize=N //設(shè)置?Metaspace?的最大大小
與永久代很大的不同就是,元空間解決了永久代易發(fā)生內(nèi)存溢出的問(wèn)題,但是要注意,如果不指定元空間的大小,隨著更多類的創(chuàng)建,虛擬機(jī)會(huì)耗盡所有可用的系統(tǒng)內(nèi)存。
?4、JVM的分代介紹
因?yàn)镚C垃圾回收的主要區(qū)域是堆區(qū),從GC的角度來(lái)說(shuō),java堆又細(xì)分為新生代和老年代,另外有一部分是持久代,來(lái)代表方法區(qū),是為了方便管理,是方法區(qū)在堆區(qū)開(kāi)辟出來(lái)的一塊邏輯空間,下圖為分代示意圖(圖片來(lái)自于網(wǎng)絡(luò))

(1)新生代,新生代又分為三個(gè)區(qū)域,包括Eden區(qū)和兩個(gè)Survivor幸存者區(qū),Eden區(qū)主要存放new出來(lái)的對(duì)象,Survivor主要存放上一次GC之后的幸存者,作為這一次GC的被掃描者,分別對(duì)應(yīng)圖中的S0和S1,兩個(gè)Survivor區(qū)等大,其中一個(gè)閑置,所以新生代實(shí)際可用的內(nèi)存大小要減去其中一個(gè)幸存者區(qū)域的大小
(2)老年代,老年代主要存放大對(duì)象,或從MinorGC過(guò)來(lái)的到達(dá)一定年齡仍然幸存的對(duì)象
(3)持久代,即方法區(qū),用于存放已被虛擬機(jī)加載的類的信息,靜態(tài)變量,常量,和編譯后的代碼等信息,持久代能被GC的是廢棄的常量,持久代對(duì)垃圾回收沒(méi)有顯著影響
?5、GC的回收過(guò)程
(1)?MinorGC的過(guò)程:MinorGC采用復(fù)制算法,首先,把Eden區(qū)域和使用的幸存者區(qū)域(SurvivorFrom)中存活的對(duì)象復(fù)制到另一塊空閑的幸存者區(qū)(SurvivorTo)中,同時(shí),把這些對(duì)象的年齡+1,如果有對(duì)象的年齡已經(jīng)達(dá)到了老年代的標(biāo)準(zhǔn),則賦值到老年代區(qū),如果空閑的幸存者區(qū)(SurvivorTo)不夠存放Eden和使用的幸存者區(qū)(SurvivorFrom)移動(dòng)過(guò)來(lái)的數(shù)據(jù),則直接放到老年代,然后,清空Eden和使用的幸存者區(qū)中(SurvivorFrom)的對(duì)象,然后,幸存者區(qū)互換,SurvivorTo中的數(shù)據(jù)換到SurvivorFrom中,SurvivorFrom繼續(xù)等待下一次GC,Survivor區(qū)每熬過(guò)一次MinorGC,就將對(duì)象的年齡+1,當(dāng)對(duì)象的年齡到達(dá)某個(gè)值時(shí)(默認(rèn)時(shí)15,可用通過(guò)參數(shù)-XX:MaxTenuringThreshold?來(lái)設(shè)定),這些對(duì)象就會(huì)成為老年代
? (2)?MajorGC的過(guò)程:老年代的回收,不會(huì)那么頻繁,老年代使用的回收算法是標(biāo)記-清除或標(biāo)記-整理算法,標(biāo)記-清除算法會(huì)產(chǎn)生內(nèi)存碎片,即不連續(xù)的空間,如果此時(shí),有大的對(duì)象進(jìn)來(lái),內(nèi)存中沒(méi)有足夠的連續(xù)空間時(shí),會(huì)提前觸發(fā)FullGC(這是一個(gè)優(yōu)化點(diǎn)),一次FullGC的時(shí)間要比一次MinorGC的時(shí)間長(zhǎng),當(dāng)年老代也裝不下,就會(huì)拋出OOM(Out Of Memory)異常 (3)?Full GC的觸發(fā):老年代滿了而無(wú)法容納更多的對(duì)象,會(huì)觸發(fā)Full GC,Full GC?清理整個(gè)內(nèi)存堆,包括年輕代和老年代。
?6、GC的回收算法
(1)?標(biāo)記-清除算法,缺點(diǎn)是容易產(chǎn)生碎片,且效率不高,標(biāo)記過(guò)程和清除過(guò)程效率都不高
標(biāo)記-清除算法的過(guò)程,分為兩個(gè)過(guò)程,標(biāo)記過(guò)程和清除過(guò)程
①?標(biāo)記過(guò)程,遍歷所有的GC-roots,然后將所有GC-roots可達(dá)的對(duì)象標(biāo)記為存活的對(duì)象(記為1)
②?清除過(guò)程,遍歷堆中的所有對(duì)象,將沒(méi)有標(biāo)記的對(duì)象全部清除掉(沒(méi)有標(biāo)記過(guò)的,記為0)
③?清除過(guò)后,被標(biāo)記過(guò)的對(duì)象留下,標(biāo)志位重新歸0
以下是標(biāo)記-清除算法的圖示(圖片來(lái)自于網(wǎng)絡(luò))
?
(2)?復(fù)制算法,用于年輕代,需要額外的空間來(lái)進(jìn)行復(fù)制操作
復(fù)制算法的過(guò)程,就是把內(nèi)存分為2塊等同大小的內(nèi)存空間(A和B),使用A進(jìn)行內(nèi)存的使用,當(dāng)A內(nèi)存不足以分配對(duì)象而引起內(nèi)存回收時(shí),就會(huì)把存活的對(duì)象從A內(nèi)存塊放到B內(nèi) 存塊,然后把A內(nèi)存塊中的對(duì)象全部清除掉,然后在B內(nèi)存塊中使用,當(dāng)B內(nèi)存不足以分配對(duì)象而引起內(nèi)存回收時(shí),就會(huì)把存活的對(duì)象從B內(nèi)存塊放到A內(nèi)存塊中,然后把B內(nèi)存 塊中的對(duì)象全部清除掉,如此循環(huán)
復(fù)制算法的好處是,避免的空間碎片(內(nèi)存中不連續(xù)的空間),缺點(diǎn)是浪費(fèi)了一半的空間,降低空間使用率
? 以下是復(fù)制算法的圖示(圖片來(lái)自于網(wǎng)絡(luò))
?
(3)?標(biāo)記-整理算法(Mark-Compact),用于老年代
標(biāo)記-整理算法的過(guò)程,標(biāo)記過(guò)程仍然與標(biāo)記-清除算法一樣,但后續(xù)步驟不是直接?對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉端邊界以 外的內(nèi)存
以下是標(biāo)記-整理算法的圖示(圖片來(lái)自于網(wǎng)絡(luò))
?
? 從圖中可以看出,標(biāo)記-整理算法,避免了標(biāo)記-清除算法產(chǎn)生內(nèi)存碎片的問(wèn)題,?也避免了復(fù)制算法中內(nèi)存浪費(fèi)的問(wèn)題,存在的問(wèn)題就是效率問(wèn)題,比前兩者效率低
(4)?分代收集,JVM實(shí)際GC中使用的,根據(jù)對(duì)象存活周期的不同,分新生代和老年代,新生代使用復(fù)制算法,因?yàn)槊看?/span>GC只有少量的對(duì)象存活,用復(fù)制算法只需要付出少量存活對(duì) 象的復(fù)制成本就可以完成收集,老年代使用標(biāo)記-整理算法或標(biāo)記-清除算法,老年代中,對(duì)象存活率高,沒(méi)有額外的擔(dān)保空間,就必須使用標(biāo)記-清除或標(biāo)記-整理算法
以下是分代收集算法的圖示(圖片來(lái)自于網(wǎng)絡(luò))

7、GC的回收器
1、收集器
(1)?serial收集器,是單線程收集器,用于新生代,使用復(fù)制算法,在GC時(shí),會(huì)暫停其他所有工作的線程,直到GC結(jié)束,常用于client模式下的虛擬機(jī)
(2)?parNew收集器,是serial的多線程版本,也用于新生代,使用復(fù)制算法
parNew和serial都可以且只能和老年代的CMS和serial old組合使用
(3)?Parallel Scarenge收集器,用于新生代,使用復(fù)制算法,主要關(guān)注吞吐量,適合在后臺(tái)運(yùn)算且不需要太多交互的任務(wù)
(4)?Serial old收集器,是serial收集器的老年代版本,是單線程收集器,使用標(biāo)記-整理算法,也是給client下的虛擬機(jī)用
(5)?Parallel old,用于老年代,使用標(biāo)記-整理算法,注重吞吐量,及CPU敏感的場(chǎng)合優(yōu)先考慮使用parallel + parallel old組合
(6)?CMS(concurrent Mark Sweep)收集器,特點(diǎn)是獲取最短回收停頓時(shí)間為目標(biāo)的收集器,使用標(biāo)記-清除算法,支持并發(fā)、低停頓,缺點(diǎn)是對(duì)CPU資源敏感(在并發(fā)階段,雖然不會(huì)導(dǎo)致用戶線程停頓,但會(huì)因?yàn)檎加靡徊糠志€程(或CPU資源),而導(dǎo)致應(yīng)用程序變慢),會(huì)導(dǎo)致吞吐量降低,且無(wú)法收集浮動(dòng)垃圾(標(biāo)記-清除算法,會(huì)產(chǎn)生大量的碎片),會(huì)導(dǎo)致FullGC,可以用serial old臨時(shí)替代
(7)?G1,JDK7中新增的回收器,是JDK9默認(rèn)的回收器,特點(diǎn)是,面向服務(wù)端應(yīng)用的垃圾收集器,支持并發(fā)與并行,充分利用CPU多核,縮短stop時(shí)間,且可預(yù)測(cè)停頓,采用分代收集,整體是標(biāo)記-整理算法,局部是復(fù)制算法,不會(huì)產(chǎn)生碎片
2、G1回收器的工作原理
(1)G1的分代收集,如下圖(圖片來(lái)自于網(wǎng)絡(luò))

? G1的分代收集,將整個(gè)堆分成n個(gè)大小相等的Region區(qū)域,每個(gè)Region占用一塊連續(xù)的虛擬內(nèi)存地址,新生代和老生代不再是物理隔離,而是一部分Region的集合,Region的大小可以通過(guò)-XX:G1HeapRegionSize設(shè)定,如果未設(shè)置,默認(rèn)是2048份,G1仍是分代收集,除Eden、Survivor、Old區(qū)域外,還包含Humongous,是專門用來(lái)存放巨型對(duì)象的,即占用空間>50%分區(qū)容量的對(duì)象,以此減少短期存在的巨型對(duì)象對(duì)垃圾收集造成的負(fù)面影響。
G1的回收過(guò)程:
(1)標(biāo)記過(guò)程,G1的標(biāo)記分為幾個(gè)階段,包括全局并發(fā)標(biāo)記,初始標(biāo)記,并發(fā)標(biāo)記,最終標(biāo)記
i. 全局并發(fā)標(biāo)記:基于 STAB(snapshot-at-the-beginning)形式的并發(fā)標(biāo)記,標(biāo)記完成后,G1知道哪個(gè)區(qū)域是空的,它首先會(huì)收集那些產(chǎn)生大量空閑空間的區(qū)域
ii.初始標(biāo)記STW:耗時(shí)很短,標(biāo)記GC-roots能直接關(guān)聯(lián)的對(duì)象,壓入掃描棧
iii.并發(fā)標(biāo)記:與用戶線程并發(fā)執(zhí)行,耗時(shí)較長(zhǎng),GC線程不斷從掃描棧中取出引用,然后遞歸標(biāo)記,直到掃描棧清空
iv.最終標(biāo)記STW:重新標(biāo)記并發(fā)標(biāo)記期間因用戶程序執(zhí)行而導(dǎo)致引用發(fā)生變動(dòng)的那部分標(biāo)記,寫入屏障Write Barrier標(biāo)記的對(duì)象
(2)清理過(guò)程:統(tǒng)計(jì)各個(gè)Region被標(biāo)記存活的對(duì)象有多少,如果發(fā)現(xiàn)沒(méi)有存活,就會(huì)整體回收到可分配的Region中
(3)拷貝存活對(duì)象:將Region中的存活對(duì)象拷貝到空Region里去,回收原Region空間,繼續(xù)使用
G1不存在Full GC,分為Yong GC和Mix GC兩種,Yong GC是新生代的回收,Mix GC是老年代的收集
8、JVM的優(yōu)化
這是很重要的一部分,JVM參數(shù)平時(shí)不需要頻繁的去調(diào)整,可以通過(guò)觀察應(yīng)用環(huán)境的運(yùn)行,定期的調(diào)整,主要有兩方面,一個(gè)是JVM參數(shù)的調(diào)整,一個(gè)是GC的優(yōu)化,GC優(yōu)化的目標(biāo)是盡量減少GC次數(shù),盡量在Yong區(qū)完成GC,盡量減少Full GC的次數(shù),以減少GC對(duì)應(yīng)用程序帶來(lái)的影響
(1)JVM參數(shù)(這里列出所有可調(diào)整的JVM參數(shù),可根據(jù)各自的環(huán)境酌情設(shè)置)
-Xms4G?是指:?JVM啟動(dòng)時(shí)整個(gè)堆(包括年輕代,年老代)的初始化大小
-Xmx4G?是指:?JVM啟動(dòng)時(shí)整個(gè)堆的最大值,默認(rèn)為物理內(nèi)存的1/4
-Xms和-Xmx的設(shè)置,默認(rèn)空余堆內(nèi)存小于?40%時(shí),JVM就會(huì)增大堆直到-Xmx的最大限制;空余堆內(nèi)存大于70%時(shí),JVM會(huì)減少堆直到-Xms的最小限制。因此服務(wù)器一般 設(shè)置-Xms、?-Xmx相等以避免在每次GC?后調(diào)整堆的大小
?
-Xmn2G是指:年輕代的空間大小,通常為?Xmx?的?1/3?或?1/4,剩下的是年老代的空間
-XX:SurvivorRatio=1是指:年輕代Eden區(qū)和單個(gè)S區(qū)的比例,默認(rèn)是8,即Eden占8/10,兩個(gè)Survivor分別占1/10,其中一個(gè)Survivor閑置,用于復(fù)制,所以年輕代實(shí)際可用的內(nèi)存大小未-Xmn設(shè)置值的9/10,Eden設(shè)置的太答的話,會(huì)導(dǎo)致GC變慢,并且沒(méi)有足夠的幸存者空間,會(huì)導(dǎo)致GC直接到達(dá)老年代,老年代滿的更快,會(huì)更早觸發(fā)Full GC,Eden設(shè)置的過(guò)小,則Minor頻繁,會(huì)影響線上程序運(yùn)行,因?yàn)镚C會(huì)導(dǎo)致應(yīng)用程序暫停
-XX:MaxTenuringThreshold,設(shè)置年輕代中回收區(qū)對(duì)象的年齡,默認(rèn)15,可通過(guò)命令指定,如果設(shè)置為0,表示Eden回收時(shí),不經(jīng)過(guò)Survivor,直接到達(dá)老年代
-XX:NewRatio,設(shè)置新生代與老年代的比例,如?–XX:NewRatio=2,則新生代占整個(gè)堆空間的1/3,老年代占2/3
(2)GC的參數(shù)優(yōu)化
i.CMS回收器的參數(shù)優(yōu)化
-XX:CMSInitiatingOccupancyFraction=70,該值代表老年代堆空間的使用率,默認(rèn)值是92,假如設(shè)置為70,就表示第一次?CMS?垃圾收集會(huì)在老年代占用?70%?時(shí)觸發(fā)。過(guò)大會(huì)使?STW?時(shí)間過(guò)長(zhǎng),過(guò)小會(huì)影響吞吐率
-XX:+UseCMSCompactAtFullCollection,-XX:CMSFullGCsBeforeCompaction=4:執(zhí)行4次不壓縮的?Full GC?后,會(huì)執(zhí)行一次內(nèi)存壓縮的過(guò)程,用來(lái)消除內(nèi)存碎片
-XX:+ConcGCThreads,并發(fā)?CMS?過(guò)程運(yùn)行時(shí)的線程數(shù),CMS?默認(rèn)回收線程數(shù)是(CPU+3) / 4。更多的線程會(huì)加快并發(fā)垃圾回收過(guò)程,但會(huì)帶來(lái)額外的同步開(kāi)銷。
ii.G1回收器的參數(shù)優(yōu)化
-XX:G1HeapRegionSize,指定G1中Rigion的大小,如果未設(shè)置,默認(rèn)將堆內(nèi)存平均分為?2048?份
-XX:MaxGCPauseMillis=n,設(shè)置GC時(shí)最大暫停時(shí)間,這個(gè)目標(biāo)不一定能滿足,JVM會(huì)盡最大努力實(shí)現(xiàn)它,不建議設(shè)置的過(guò)小(<50ms)
-XX:InitiatingHeapOccupancyPercent=n,觸發(fā)G1啟動(dòng)?Mixed GC,表示垃圾對(duì)象在整個(gè)G1?堆內(nèi)存空間的占比??
避免使用?-Xmn?或?-XX:NewRatio等其他顯式設(shè)置年輕代大小的選項(xiàng),固定年?輕代大小,會(huì)覆蓋暫停時(shí)間目標(biāo)
9、JVM的內(nèi)存分析工具?
JVM自帶的內(nèi)存分析小工具:jconsole、jhat、jmap、jstack、jstat、jstatd、jvisualvm
linux工具:pidstat、vmstat、iostat
eclipse的分析工具:mat
收費(fèi)工具:Jporfiler,yourkit
?
(1)?jconsole,jvm自帶內(nèi)存分析工具,位于jdk的bin目錄下,它提供了圖形界面。可以查看到被監(jiān)控的jvm的內(nèi)存信息,線程信息,類加載信息,MBean信息。
(2)?jhat,jvm自帶內(nèi)存分析工具,位于jdk的bin目錄下,jdk6+版本自帶,能夠分析dump文件,執(zhí)行?jhat -J -Xmx512m [file]?,file就是dump文件路徑。
(3)?jmap,jvm自帶內(nèi)存分析工具,位于jdk的bin目錄下,傾向于分析jvm內(nèi)存中對(duì)象信息,jmap -histo
jmap -dump:file=c:\dump.txt 340??導(dǎo)出dump文件,用專門的dump分析工具分析。
(4)?jstack,jvm自帶內(nèi)存分析工具,位于jdk的bin目錄下,會(huì)顯示線程優(yōu)先級(jí),線程ID,native線程ID,線程棧起始地址
(5)?jstat,jvm自帶內(nèi)存分析工具,位于jdk的bin目錄下,傾向于分析jvm內(nèi)存的gc情況,常用參數(shù)-gcutil,這個(gè)參數(shù)的作用不斷的顯示當(dāng)前指定的jvm內(nèi)存的垃圾收集的信息。?jstat -gcutil 340 10000,這個(gè)命令是每個(gè)10秒鐘輸出一次jvm的gc信息,10000指的是間隔時(shí)間為10000毫秒。
(6)?jstatd,jvm自帶內(nèi)存分析工具,位于jdk的bin目錄下,一個(gè)RMI的server,它可以監(jiān)控Hotspot的JVM的啟動(dòng)和結(jié)束,同時(shí)提供接口可以讓遠(yuǎn)程機(jī)器連接到JVM。比如?jps ?jstat都可以通過(guò)jstatd來(lái)遠(yuǎn)程觀察JVM的運(yùn)行情況。
(7)?jvisualvm,jvm自帶內(nèi)存分析工具,位于jdk的bin目錄下,JDK6 update 7之后推出,,java可視化虛擬機(jī),它不但提供了jconsole類似的功能,還提供了jvm內(nèi)存和cpu實(shí)時(shí)診斷,還有手動(dòng)dump出jvm內(nèi)存情況,手動(dòng)執(zhí)行gc。和jconsole一樣,運(yùn)行jviusalvm,在jdk的bin目錄下執(zhí)行jvisualvm,windows下是jvisualvm.exe,linux和unix下是jvisualvm.sh。
(8)?pidstat,linux系統(tǒng)下使用,需要安裝,yum install sysstat,要查看Linux下面進(jìn)程、進(jìn)程組、線程的資源消耗的統(tǒng)計(jì)信息,可以使用pidstat,它可以收集并報(bào)告進(jìn)程的統(tǒng)計(jì)信息。
(9)?vmstat,linux系統(tǒng)下使用,需要安裝,vmstat是Virtual Meomory Statistics(虛擬內(nèi)存統(tǒng)計(jì))的縮寫,可對(duì)操作系統(tǒng)的虛擬內(nèi)存、進(jìn)程、CPU活動(dòng)進(jìn)行監(jiān)控。是對(duì)系統(tǒng)的整體情況進(jìn)行統(tǒng)計(jì),不足之處是無(wú)法對(duì)某個(gè)進(jìn)程進(jìn)行深入分析。
(10)?iostat,linux系統(tǒng)下使用,需要安裝,yum install sysstat,iostat是I/O statistics(輸入/輸出統(tǒng)計(jì))的縮寫,iostat工具將對(duì)系統(tǒng)的磁盤操作活動(dòng)進(jìn)行監(jiān)視。它的特點(diǎn)是匯報(bào)磁盤活動(dòng)統(tǒng)計(jì)情況,同時(shí)也會(huì)匯報(bào)出CPU使用情況。iostat也有一個(gè)弱點(diǎn),就是它不能對(duì)某個(gè)進(jìn)程進(jìn)行深入分析,僅對(duì)系統(tǒng)的整體情況進(jìn)行分析
(11)?MAT,可以通過(guò)MAT分析內(nèi)存泄漏的原因
10、對(duì)象的創(chuàng)建
在語(yǔ)言層面,創(chuàng)建對(duì)象有四種方式:1) clone 2)反序列化 3)反射 4) New
? 而在虛擬機(jī)中,對(duì)象創(chuàng)建的過(guò)程是如何呢?
? JAVA編譯解釋的過(guò)程:.java文件->javac編譯成.class字節(jié)碼文件->jvm解釋執(zhí)行。
?? Java很特殊,Java程序需要編譯但是沒(méi)有直接編譯成機(jī)器語(yǔ)言,即二進(jìn)制語(yǔ)言,而是編譯成字節(jié)碼(.class)再用解釋方式執(zhí)行。java程序編譯以后的class屬于中間代碼,并不是可執(zhí)行程序exe,不是二進(jìn)制文件,所以在執(zhí)行的時(shí)候需要一個(gè)中介來(lái)解釋中間代碼,這既是java解釋器,也就是所謂的java虛擬機(jī)(JVM),也叫JDK。
?
JVM中,對(duì)象創(chuàng)建過(guò)程(New)分三步:1) 類加載 2) 為新對(duì)象分配內(nèi)存 3)初始化
? 在虛擬機(jī)遇到new指令時(shí):
1)?類加載:確保常量池中存放的時(shí)已解釋的類,且對(duì)象所屬類型已經(jīng)初始化過(guò),如果沒(méi)有,則先執(zhí)行類加載
2)?為新生對(duì)象分配內(nèi)存:對(duì)象所需內(nèi)存大小在類加載時(shí)可以確定,將確定大小的內(nèi)存從Java堆中劃分出來(lái)
分配空閑內(nèi)存方法:
指針碰撞:假如堆是規(guī)整的,用過(guò)的內(nèi)存和空閑的內(nèi)存各一邊,中間使用指針作為分界點(diǎn),分配內(nèi)存時(shí)將指針移動(dòng)對(duì)象大小的距離
空閑列表:假如堆是不規(guī)整的,虛擬機(jī)需要維護(hù)哪些內(nèi)存塊是可用的列表,分配時(shí)候從列表中找出足夠大的空閑內(nèi)存劃分,并更新列表記錄
對(duì)象創(chuàng)建在并發(fā)情況下保證線程安全:例如,正在給對(duì)象A分配內(nèi)存,指針還沒(méi)修改,對(duì)象B同時(shí)使用了原來(lái)的指針來(lái)分配內(nèi)存
CAS配上失敗重試
本地線程分配緩沖TLAB(ThreadLocal Allocation Buffer):將內(nèi)存分配動(dòng)作按線程劃分到不同空間中進(jìn)行,即每個(gè)線程在Java堆中預(yù)先分配一塊內(nèi)存
3)?將分配的內(nèi)存空間初始化為零值:保證對(duì)象的實(shí)例在Java代碼中可以不賦值就可??直接使用,能訪問(wèn)到這些字段的數(shù)據(jù)類型對(duì)應(yīng)的零值(例如,int類型參數(shù)默認(rèn)為0)
4)?設(shè)置對(duì)象頭:設(shè)置對(duì)象的類的元數(shù)據(jù)信息、哈希碼、GC分代年齡等
5)?執(zhí)行
?
?11、對(duì)象的內(nèi)存布局
在HotSpot虛擬機(jī)中,對(duì)象在內(nèi)存中存儲(chǔ)的布局分為3個(gè)區(qū)域,如下圖:
?
? (1)對(duì)象頭(Header):
i.?MarkWord:存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),例如:哈希碼HashCode、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程ID等。考慮空間效率,Mark設(shè)計(jì)為非固定的數(shù)據(jù)結(jié)構(gòu),它根據(jù)對(duì)象的不同狀態(tài)復(fù)用自己的空間,如下表格:

? ii.?指向Class的指針:即對(duì)象指向它的類的元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定是哪個(gè)類的實(shí)例
iii. 如果對(duì)象是Java數(shù)組,對(duì)象頭中還需要一塊記錄數(shù)組長(zhǎng)度的數(shù)據(jù)
(2) 實(shí)例數(shù)據(jù)(Instance Data):對(duì)象真正存儲(chǔ)的有效信息,也是程序代碼中定義的各種類型字段的內(nèi)容
(3) 對(duì)齊填充(Padding):起占位符的作用,因?yàn)?/span>HotSpot VM的要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍,也就是對(duì)象的大小必須是8字節(jié)的整數(shù)倍,當(dāng)對(duì)象實(shí)例數(shù)據(jù)部分沒(méi)有對(duì)齊時(shí),需要對(duì)齊填充來(lái)補(bǔ)充
?
??12、對(duì)象的監(jiān)視器
什么是對(duì)象監(jiān)視器,監(jiān)視器是一種同步結(jié)構(gòu),它基于互斥鎖,允許線程同時(shí)互斥(使用?鎖)和協(xié)作
? 互斥是,當(dāng)一個(gè)線程訪問(wèn)受保護(hù)的數(shù)據(jù)時(shí),如果沒(méi)有其他線程在等待,?線程獲取鎖?并繼續(xù)執(zhí)行。當(dāng)線程完成執(zhí)行時(shí),它釋放鎖并退出監(jiān)視器。但如果此時(shí)另一個(gè)線程已經(jīng)擁有監(jiān)視器時(shí),它必須在entry-set中等待。當(dāng)前面的線程?執(zhí)行完畢退出監(jiān)視器時(shí),新到達(dá)的線程必須與在入口集中等待的其他線程競(jìng)爭(zhēng)。只有一?個(gè)線程能贏得競(jìng)爭(zhēng)并擁有鎖。
? 協(xié)作是,當(dāng)一個(gè)線程需要數(shù)據(jù)在某一個(gè)狀態(tài)下它才能執(zhí)行,那么另一個(gè)線程負(fù)責(zé)將數(shù)據(jù)?改變到此狀態(tài)
?
對(duì)象監(jiān)視器的一個(gè)理解:對(duì)象監(jiān)視器,任意線程對(duì)Object的訪問(wèn),首先要先獲得Object?的監(jiān)視器。如果獲取失敗了,線程進(jìn)入同步隊(duì)列,線程狀態(tài)變?yōu)?/span>BLOCKED。當(dāng)訪問(wèn)Object?的線程(獲得了所的線程)釋放了鎖,則該釋放操作喚醒在同步隊(duì)列中的線程,使其重?新嘗試對(duì)監(jiān)視器的獲取。Thread類提供一個(gè)holdsLock(Object obj)方法,當(dāng)且僅當(dāng)對(duì)象?obj的監(jiān)視器被某條線程持有的時(shí)候才返回true,注意這是一個(gè)static方法,意味著某?條線程指的是當(dāng)前線程
?
常見(jiàn)的如生產(chǎn)者/消費(fèi)者的問(wèn)題,當(dāng)讀線程需要緩沖區(qū)處于“不空”的狀態(tài)它才可以從?緩沖區(qū)中讀取任何數(shù)據(jù),如果它發(fā)現(xiàn)緩沖區(qū)為空,則進(jìn)入wait-set等待。待寫線程用數(shù)?據(jù)填充緩沖區(qū),再通知讀線程進(jìn)行讀取。這種機(jī)制被稱為“Wait and Notify”或“Signal?and Continue”
?
? 下圖描述了對(duì)象、對(duì)象的監(jiān)視器、同步隊(duì)列和執(zhí)行線程之間的關(guān)系。

?
? 從上圖中可以看到,任意線程對(duì)Object(Object由synchronized保護(hù))的訪問(wèn),首先?要獲得Object的監(jiān)視器。如果獲取失敗,線程進(jìn)入同步隊(duì)列,線程狀態(tài)變?yōu)?/span>BLOCKED。?當(dāng)訪問(wèn)Object的前驅(qū)(獲得了鎖的線程)釋放了鎖,則該釋放操作喚醒阻塞在同步隊(duì)?列中的線程,使其重新嘗試對(duì)監(jiān)視器的獲取。
?
那么,對(duì)象的監(jiān)視器到底是做什么的,用在哪里,起什么作用?
為什么使用術(shù)語(yǔ)“監(jiān)視器”而不是“鎖定”?嚴(yán)格來(lái)說(shuō),確實(shí)不同,“鎖”是指具有獲取和釋放原語(yǔ)的東西,這些原語(yǔ)和原語(yǔ)保持某些鎖屬性。例如,獨(dú)?占使用或單作者/多讀者。
? “監(jiān)視程序”是一種機(jī)制,可確保在任何給定時(shí)間只有一個(gè)線程可以執(zhí)行給定的代碼?段。可以使用鎖(和“條件變量”,允許線程等待或向其他線程發(fā)送滿足條件的通知)?來(lái)實(shí)現(xiàn)此功能,但它不僅僅是鎖。實(shí)際上,在Java情況下,不能直接訪問(wèn)監(jiān)視器?使用的實(shí)際鎖。(您不能說(shuō)“ Object.lock()”來(lái)阻止其他線程獲取它,就像使用Java?Lock實(shí)例一樣。)
?
簡(jiǎn)而言之,如果要學(xué)究的話,“ monitor”實(shí)際上是比“ lock”更好的術(shù)語(yǔ),用于描述?Java提供的特性。但是實(shí)際上,這兩個(gè)術(shù)語(yǔ)幾乎可以互換使用。
13、類加載器、反射、雙親委派
(1)類加載器,其作用就是,負(fù)責(zé)將class文件加載到內(nèi)存中
java中,先通過(guò)javac將java文件編譯為class文件,?然后使用ClassLoader類加載器加載class文件到內(nèi)存中,當(dāng)一個(gè)類被使用時(shí),就會(huì)加載到內(nèi)存中,類加載的過(guò)程包括:加載,驗(yàn)證,準(zhǔn)備,初始化
(2)?雙親委派模型
每一個(gè)類都有一個(gè)相對(duì)應(yīng)的類加載器,系統(tǒng)中的ClassLoader在協(xié)同工作會(huì)默認(rèn)使用雙親委派模型,類加載的過(guò)程,是從父類到子類,類驗(yàn)證的過(guò)程,是從子類到父類,也就是說(shuō),加載的時(shí)候,首先把該類委派給該父類加載器的loadClass()處理,因此所有的請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器BootstrapClassLoader中,然后驗(yàn)證,即在類加載的時(shí)候,系統(tǒng)就會(huì)首先判斷當(dāng)前類是否被加載過(guò),如果已經(jīng)加載的類會(huì)直接返回,否則都會(huì)嘗試加載,即當(dāng)父類加載器無(wú)法處理時(shí),都由自己來(lái)處理,當(dāng)父類加載器為null時(shí),會(huì)使用啟動(dòng)類加載器BoostrapClassLoader作為父類加載器。
雙親委派保證了java程序的穩(wěn)定運(yùn)行,避免了類的重復(fù)加載。
除了BootstrapClassLoaderader其他類加載器均由Java實(shí)現(xiàn)且全部繼承于java.lang.ClassLoader,如果要自定義類加載器,就要繼承ClassLoader。
(3)反射
反射是把.class文件加載進(jìn)內(nèi)存,把.class中的所有內(nèi)容,封裝成?一個(gè)一個(gè)的對(duì)象的過(guò)程,在?程序中可以通過(guò)這些對(duì)象動(dòng)態(tài)的完成方法的調(diào)用,成員變量的賦值,對(duì)象的創(chuàng)建!
反射獲取Class對(duì)象的三種方式:
i、Class.forName(全類名)
ii、類名.class
iii、對(duì)象名.getClass();
source:https://www.cnblogs.com/ll-love/p/13961756.html
最近熬夜給大家準(zhǔn)備了515套Java代碼,有一些是業(yè)務(wù)類的小項(xiàng)目,比如Java博客項(xiàng)目,也有腳手架、也有平時(shí)用一些的工具類、21套小程序代碼,也有一些游戲類的項(xiàng)目。
掃以下二維碼并回復(fù)“828”即可獲取
或者在本公眾號(hào)對(duì)話框回復(fù)【828】馬上獲取

