1. JVM(https://blog.csdn.net/qq_41701956/article/details/81664921)
JVM的作用:解釋運行字節(jié)碼程序消除平臺相關(guān)性扼鞋。jvm將java字節(jié)碼解釋為具體平臺的具體指令申鱼。一般的高級語言如要在不同的平臺上運行,至少需要編譯成不同的目標(biāo)代碼云头。而引入JVM后捐友,Java語言在不同平臺上運行時不需要重新編譯。Java語言使用模式Java虛擬機屏蔽了與具體平臺相關(guān)的信息溃槐,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標(biāo)代碼(字節(jié)碼)匣砖,就可以在多種平臺上不加修改地運行。Java虛擬機在執(zhí)行字節(jié)碼時昏滴,把字節(jié)碼解釋成具體平臺上的機器指令執(zhí)行猴鲫。
JVM常見問題:https://mp.weixin.qq.com/s/Xo3_ZTVruhFSTMSrmJLvrw
-
JVM知識結(jié)構(gòu)圖
image.png -
JVM內(nèi)存結(jié)構(gòu)圖
image.png 程序計數(shù)器:內(nèi)存空間小,線程私有谣殊。字節(jié)碼解釋器工作是就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行指令的字節(jié)碼指令拂共,分支、循環(huán)姻几、跳轉(zhuǎn)宜狐、異常處理势告、線程恢復(fù)等基礎(chǔ)功能都需要依賴計數(shù)器完成。唯一一個在 Java 虛擬機規(guī)范中沒有規(guī)定任何 OutOfMemoryError 情況的區(qū)域抚恒。
虛擬機棧:線程私有咱台,生命周期和線程一致。描述的是 Java 方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行時都會床創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表俭驮、操作數(shù)棧回溺、動態(tài)鏈接、方法出口等信息混萝。每一個方法從調(diào)用直至執(zhí)行結(jié)束遗遵,就對應(yīng)著一個棧幀從虛擬機棧中入棧到出棧的過程。
本地方法棧:Java 虛擬機棧為虛擬機執(zhí)行 Java 方法(也就是字節(jié)碼)服務(wù)逸嘀,而本地方法棧則為虛擬機使用到的 Native 方法服務(wù)瓮恭。
堆:對于絕大多數(shù)應(yīng)用來說,這塊區(qū)域是 JVM 所管理的內(nèi)存中最大的一塊厘熟。線程共享,主要是存放對象實例和數(shù)組维哈。內(nèi)部會劃分出多個線程私有的分配緩沖區(qū)(Thread Local Allocation Buffer, TLAB)绳姨。可以位于物理上不連續(xù)的空間阔挠,但是邏輯上要連續(xù)飘庄。
方法區(qū):屬于共享內(nèi)存區(qū)域,存儲已被虛擬機加載的類信息购撼、常量跪削、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)迂求。
運行時常量池:屬于方法區(qū)一部分碾盐,用于存放編譯期生成的各種字面量和符號引用。編譯器和運行期(String 的 intern() )都可以將常量放入池中揩局。內(nèi)存有限毫玖,無法申請時拋出 OutOfMemoryError。
直接內(nèi)存:非虛擬機運行時數(shù)據(jù)區(qū)的部分
2. 虛擬機棧結(jié)構(gòu)
3. Java內(nèi)存模型(JMM)
??Java內(nèi)存模型(簡稱JMM)凌盯,是一種規(guī)范付枫。JMM決定一個線程對共享變量的寫入何時對另一個線程可見。從抽象的角度來看驰怎,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲在主內(nèi)存(main memory)中阐滩,每個線程都有一個私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本县忌。線程對變量的操作只能在各自線程的工作內(nèi)存操作掂榔。
JMM的三大特性:
- 可見性
- 原子性
-
有序性
本地內(nèi)存是JMM的一個抽象概念继效,并不真實存在。它涵蓋了緩存衅疙,寫緩沖區(qū)莲趣,寄存器以及其他的硬件和編譯器優(yōu)化。其關(guān)系模型圖如下圖所示:
image.png
4. JVM 調(diào)優(yōu)饱溢,查看 JVM 參數(shù)默認(rèn)值
- jps -v 可以查看 jvm 進程顯示指定的參數(shù)
- 使用 -XX:+PrintFlagsFinal 可以看到 JVM 所有參數(shù)的值
- jinfo 可以實時查看和調(diào)整虛擬機各項參數(shù)
- jstat -gc 12538 5000即會每5秒一次顯示進程號為12538的java進程的GC情況
5. OOM排查方法
1)先查看應(yīng)用進程號pid:ps -ef | grep 應(yīng)用名
2)查看pid垃圾回收情況: jstat -gc pid 5000(時間間隔)
3)開啟OOM快照:
-XX:+HeapDumpOnOutOfMemoryError(開啟堆快照)
-XX:HeapDumpPath=C:/m.hprof(保存文件到哪個目錄)
4)dump 查看方法棧信息:jstack -l pid > /home/test/jstack.txt
5)dump 查看JVM內(nèi)存分配以及使用情況:jmap -heap pid > /home/test/jmapHeap.txt
6)dump jvm二進制的內(nèi)存詳細(xì)使用情況
6. 類加載
-
類加載器:
image.png -
類加載過程:加載喧伞,驗證,準(zhǔn)備绩郎,解析潘鲫,初始化
image.png 加載:加載是類加載過程中的一個階段,這個階段會在內(nèi)存中生成一個代表這個類的java.lang.Class對象肋杖,作為方法區(qū)這個類的各種數(shù)據(jù)的入口溉仑。
驗證:確保Class文件的字節(jié)流中包含的信息是否符合當(dāng)前虛擬機的要求,并且不會危害虛擬機自身的安全状植。
準(zhǔn)備:準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量的初始值階段浊竟,即在方法區(qū)中分配這些變量所使用的內(nèi)存空間。
解析:解析階段是指虛擬機將常量池中的符號引用替換為直接引用的過程。
初始化:初始化階段是執(zhí)行類構(gòu)造器<client>方法的過程。到了初始階段箍土,才開始真正執(zhí)行類中定義的Java程序代碼。
-
雙親委派模型
- 定義:除了頂層的啟動類加載器外后频,其余的類加載器都應(yīng)有自己的父類加載器,這里類加載器之間的父子關(guān)系一般通過組合(Composition)關(guān)系來實現(xiàn)暖途,而不是通過繼承(Inheritance)的關(guān)系實現(xiàn)卑惜。
- 工作過程:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載驻售,而是把這個請求委派給父類加載器露久,每一個層次的加載器都是如此,依次遞歸芋浮,因此所有的加載請求最終都應(yīng)該傳送到頂層的啟動類加載器中抱环,只有當(dāng)父加載器反饋自己無法完成此加載請求(它搜索范圍中沒有找到所需類)時,子加載器才會嘗試自己加載纸巷。
- 優(yōu)點:使用雙親委派模型來組織類加載器之間的關(guān)系镇草,使得Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系。例如類java.lang.Object瘤旨,它存放再rt.jar中梯啤,無論哪個類加載器要加載這個類,最終都是委派給處于模型最頂端的啟動類加載器進行加載存哲,因此Object類在程序的各種類加載器環(huán)境中都是同一個類因宇。
- 打破雙親委派機制的方法:重寫loadclass()方法
- 打破雙親委派機制的例子:
- Tomcat七婴,應(yīng)用的類加載器優(yōu)先自行加載應(yīng)用目錄下的 class,并不是先委派給父加載器察滑,加載不了才委派給父加載器打厘。打破的目的是為了完成應(yīng)用間的類隔離。
- JDK 9贺辰,Extension ClassLoader 被 Platform ClassLoader 取代户盯,當(dāng)平臺及應(yīng)用程序類加載器收到類加載請求,在委派給父加載器加載前饲化,要先判斷該類是否能夠歸屬到某一個系統(tǒng)模塊中莽鸭,如果可以找到這樣的歸屬關(guān)系,就要優(yōu)先委派給負(fù)責(zé)那個模塊的加載器完成加載吃靠。打破的原因硫眨,是為了添加模塊化的特性。
- 沙箱安全機制:防止惡意代碼污染java源代碼巢块。
7. JVM加載class文件的原理
??JVM中類的裝載是由ClassLoader和它的子類來實現(xiàn)的礁阁,Java ClassLoader 是一個重要的Java運行時系統(tǒng)組件,它負(fù)責(zé)在運行時查找和裝入類文件的類族奢。
??Java中的所有類氮兵,都需要由類加載器裝載到JVM中才能運行。類加載器本身也是一個類歹鱼,而它的工作就是把class文件從硬盤讀取到內(nèi)存中。在寫程序的時候卜高,我們幾乎不需要關(guān)心類的加載弥姻,因為這些都是隱式裝載的,除非我們有特殊的用法掺涛,像是反射庭敦,就需要顯式的加載所需要的類。
??類裝載方式薪缆,有兩種
- 隱式裝載秧廉,程序在運行過程中當(dāng)碰到通過new 等方式生成對象時,隱式調(diào)用類裝載器加載對應(yīng)的類到j(luò)vm中
- 顯式裝載拣帽,通過class.forname()等方法疼电,顯式加載需要的類,隱式加載與顯式加載的區(qū)別:兩者本質(zhì)是一樣的减拭。
??Java類的加載是動態(tài)的蔽豺,它并不會一次性將所有類全部加載后再運行,而是保證程序運行的基礎(chǔ)類(像是基類)完全加載到j(luò)vm中拧粪,至于其他類修陡,則在需要的時候才加載沧侥。這當(dāng)然就是為了節(jié)省內(nèi)存開銷。
8. GC Roots
作為GCRoots的對象包括下面幾種:
1)虛擬機棧(棧幀中的局部變量區(qū)魄鸦,也叫做局部變量表)中引用的對象宴杀。
2)方法區(qū)中的類靜態(tài)屬性引用的對象。
3)方法區(qū)中常量引用的對象拾因。
4)本地方法棧中JNI(Native方法)引用的對象旺罢。
9. 對象可達(dá)性分析
- 如果對象在進行可達(dá)性分析后發(fā)現(xiàn)沒有與GCRoots相連的引用鏈,則該對象被第一次標(biāo)記并進行一次篩選盾致,篩選條件為是否有必要執(zhí)行該對象的finalize方法主经,若對象沒有覆蓋finalize方法或者該finalize方法是否已經(jīng)被虛擬機執(zhí)行過了,則均視作不必要執(zhí)行該對象的finalize方法庭惜,即該對象將會被回收罩驻。反之,若對象覆蓋了finalize方法并且該finalize方法并沒有被執(zhí)行過护赊,那么惠遏,這個對象會被放置在一個叫F-Queue的隊列中,之后會由虛擬機自動建立的骏啰、優(yōu)先級低的Finalizer線程去執(zhí)行节吮,而虛擬機不必要等待該線程執(zhí)行結(jié)束,即虛擬機只負(fù)責(zé)建立線程判耕,其他的事情交給此線程去處理透绩。
-
對F-Queue中對象進行第二次標(biāo)記,如果對象在finalize方法中拯救了自己壁熄,即關(guān)聯(lián)上了GCRoots引用鏈帚豪,如把this關(guān)鍵字賦值給其他變量,那么在第二次標(biāo)記的時候該對象將從“即將回收”的集合中移除草丧,如果對象還是沒有拯救自己狸臣,那就會被回收。
image.png
10. 垃圾回收機制
??堆分為新生代和老年代昌执,新生代默認(rèn)占總空間的 1/3烛亦,老年代默認(rèn)占 2/3。新生代使用復(fù)制算法懂拾,有 3 個分區(qū):Eden煤禽、To Survivor、From Survivor岖赋,它們的默認(rèn)占比是 8:1:1呜师。
??當(dāng)新生代中的 Eden 區(qū)內(nèi)存不足時,就會觸發(fā) Minor GC贾节,過程如下:
1)在 Eden 區(qū)執(zhí)行了第一次 GC 之后汁汗,存活的對象會被移動到其中一個 Survivor 分區(qū)衷畦;
2)Eden 區(qū)再次 GC,這時會采用復(fù)制算法知牌,將 Eden 和 from 區(qū)一起清理祈争,存活的對象會被復(fù)制到 to 區(qū);
3)移動一次角寸,對象年齡加 1菩混,對象年齡大于一定閥值會直接移動到老年代
4)Survivor 區(qū)相同年齡所有對象大小的總和 > (Survivor 區(qū)內(nèi)存大小 * 目標(biāo)使用率)時,大于或等于該年齡的對象直接進入老年代扁藕。其中這個使用率通過 -XX:TargetSurvivorRatio 指定沮峡,默認(rèn)為 50%
5)Survivor 區(qū)內(nèi)存不足會發(fā)生擔(dān)保分配
6)年齡超過指定大小的對象可以直接進入老年代
7)Major GC,指的是老年代的垃圾清理亿柑,但并未找到明確說明何時在進行Major GC
8)FullGC邢疙,整個堆的垃圾收集,觸發(fā)條件:
- 每次晉升到老年代的對象平均大小>老年代剩余空間
- MinorGC后存活的對象超過了老年代剩余空間
- 元空間不足
- System.gc()
- CMS GC異常望薄,promotion failed:MinorGC時疟游,survivor空間放不下,對象只能放入老年代痕支,而老年代也放不下造成颁虐;concurrent mode failure:GC時,同時有對象要放入老年代卧须,而老年代空間不足造成
- 堆內(nèi)存分配很大的對象
11. 垃圾回收算法
- 引用計數(shù)法:給對象中添加一個引用計數(shù)器另绩,每當(dāng)一個地方引用這個對象時,計數(shù)器值+1花嘶;當(dāng)引用失效時板熊,計數(shù)器值-1。任何時刻計數(shù)值為0的對象就是不可能再被使用的察绷。
- 缺點:(1)每次給對象賦值時都要維護引用計數(shù)器,且計數(shù)器本身也有一定的消耗津辩;(2)較難處理循環(huán)引用
- 復(fù)制算法:(年輕代)
- 優(yōu)點:(1)不產(chǎn)生內(nèi)存碎片拆撼;(2)速度快
- 缺點:浪費10%的內(nèi)存空間
- 標(biāo)記清除算法(老年代):先標(biāo)記出要回收的對象,然后統(tǒng)一回收這些對象
- 優(yōu)點:節(jié)省空間
- 缺點:產(chǎn)生內(nèi)存碎片喘沿;
- 標(biāo)記整理算法(老年代):先標(biāo)記清除闸度,再次掃描并往一端滑動存活對象。
- 優(yōu)點:不產(chǎn)生內(nèi)存碎片蚜印;
- 缺點:需要移動對象的成本莺禁,耗時嚴(yán)重
12. 垃圾收集器
1)Serial收集器:新生代收集器,使用停止復(fù)制算法窄赋,使用一個線程進行GC哟冬,其它工作線程暫停楼熄。使用-XX:+UseSerialGC可以使用Serial+Serial Old模式運行進行內(nèi)存回收(這也是虛擬機在Client模式下運行的默認(rèn)值)
2)Serial Old收集器:老年代收集器,單線程收集器浩峡,使用標(biāo)記整理(整理的方法是Sweep(清理)和Compact(壓縮)可岂,清理是將廢棄的對象干掉,只留幸存的對象翰灾,壓縮是將移動對象缕粹,將空間填滿保證內(nèi)存分為2塊,一塊全是對象纸淮,一塊空閑)算法平斩,使用單線程進行GC,其它工作線程暫停(注意咽块,在老年代中進行標(biāo) 記整理算法清理绘面,也需要暫停其它線程),在JDK1.5之前糜芳,Serial Old收集器與ParallelScavenge搭配使用飒货。
3)ParNew收集器:新生代收集器,使用停止復(fù)制算法峭竣,Serial收集器的多線程版塘辅,用多個線程進行GC,其它工作線程暫停皆撩,關(guān)注縮短垃圾收集時間扣墩。使用-XX:+UseParNewGC開關(guān)來控制使用ParNew+Serial Old收集器組合收集內(nèi)存;使用-XX:ParallelGCThreads來設(shè)置執(zhí)行內(nèi)存回收的線程數(shù)扛吞。
4)Parallel Scavenge 收集器:新生代收集器呻惕,使用停止復(fù)制算法,關(guān)注CPU吞吐量滥比,即運行用戶代碼的時間/總時間亚脆,比如:JVM運行100分鐘,其中運行用戶代碼99分鐘盲泛,垃 圾收集1分鐘濒持,則吞吐量是99%,這種收集器能最高效率的利用CPU寺滚,適合運行后臺運算(關(guān)注縮短垃圾收集時間的收集器柑营,如CMS,等待時間很少村视,所以適 合用戶交互官套,提高用戶體驗)。使用-XX:+UseParallelGC開關(guān)控制使用 Parallel Scavenge+Serial Old收集器組合回收垃圾(這也是在Server模式下的默認(rèn)值);使用-XX:GCTimeRatio來設(shè)置用戶執(zhí)行時間占總時間的比例奶赔,默認(rèn)99惋嚎,即 1%的時間用來進行垃圾回收。使用-XX:MaxGCPauseMillis設(shè)置GC的最大停頓時間(這個參數(shù)只對Parallel Scavenge有效)
5)Parallel Old收集器:老年代收集器纺阔,多線程瘸彤,多線程機制與Parallel Scavenge差不錯,使用標(biāo)記整理(與Serial Old不同笛钝,這里的整理是Summary(匯總)和Compact(壓縮)质况,匯總的意思就是將幸存的對象復(fù)制到預(yù)先準(zhǔn)備好的區(qū)域,而不是像Sweep(清 理)那樣清理廢棄的對象)算法玻靡,在Parallel Old執(zhí)行時结榄,仍然需要暫停其它線程。Parallel Old在多核計算中很有用囤捻。Parallel Old出現(xiàn)后(JDK 1.6)臼朗,與Parallel Scavenge配合有很好的效果,充分體現(xiàn)Parallel Scavenge收集器吞吐量優(yōu)先的效果蝎土。使用-XX:+UseParallelOldGC開關(guān)控制使用Parallel Scavenge +Parallel Old組合收集器進行收集视哑。
6)CMS(Concurrent Mark Sweep)收集器:老年代收集器,致力于獲取最短回收停頓時間誊涯,使用標(biāo)記清除算法挡毅,多線程,優(yōu)點是并發(fā)收集(用戶線程可以和GC線程同時工作)暴构,停頓小跪呈。使用-XX:+UseConcMarkSweepGC進行ParNew+CMS+Serial Old進行內(nèi)存回收,優(yōu)先使用ParNew+CMS(原因見后面)取逾,當(dāng)用戶線程內(nèi)存不足時耗绿,采用備用方案Serial Old收集。解決內(nèi)存碎片問題:讓CMS在進行一定次數(shù)的Full GC(標(biāo)記清除)的時候進行一次標(biāo)記整理算法砾隅。
- 初始標(biāo)記:標(biāo)記GC roots直接關(guān)聯(lián)的對象误阻,速度快
- 并發(fā)標(biāo)記:GC Roots Tracing過程
- 重新標(biāo)記:修正并發(fā)標(biāo)記期間用戶進程繼續(xù)運行而產(chǎn)生變化的標(biāo)記,耗時比初始標(biāo)記長晴埂,但遠(yuǎn)小于并發(fā)標(biāo)記
- 并發(fā)清除:清除標(biāo)記的對象
7)G1(Garbage First)收集器:區(qū)域化垃圾收集器究反,物理上不區(qū)分新生代和老年代。采用標(biāo)記整理的算法邑时,與CMS相比:(1)不會產(chǎn)生內(nèi)存碎片;(2)用戶可以指定垃圾回收時間特姐。G1 收集器晶丘,相關(guān)配置如下:
-Xmx12g
-Xms12g
-XX:+UseG1GC
-XX:InitiatingHeapOccupancyPercent=45
-XX:MaxGCPauseMillis=200
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=256m
-XX:MaxDirectMemorySize=512m
- G1將新生代,老年代的物理空間劃分取消了。
-
G1算法將堆劃分為若干個區(qū)域(Region)浅浮,它仍然屬于分代收集器沫浆。不過,這些區(qū)域的一部分包含新生代滚秩,新生代的垃圾收集依然采用暫停所有應(yīng)用線程的方式专执,將存活對象拷貝到老年代或者Survivor空間。老年代也分成很多區(qū)域郁油,G1收集器通過將對象從一個區(qū)域復(fù)制到另外一個區(qū)域本股,完成了清理工作。H區(qū)域可以是連續(xù)的用來分配比較大的對象
image.png
8)ZGC
ZGC給Hotspot Garbage Collectors增加了兩種新技術(shù):著色指針和讀屏障桐腌。ZGC的標(biāo)記分為三個階段拄显。
- 第一階段是STW,其中GC roots被標(biāo)記為活對象案站。
- 第二階段躬审,同時遍歷對象圖并標(biāo)記所有可訪問的對象。在此階段期間蟆盐,讀屏障針使用掩碼測試所有已加載的引用承边,該掩碼確定它們是否已標(biāo)記或尚未標(biāo)記,如果尚未標(biāo)記引用石挂,則將其添加到隊列以進行標(biāo)記博助。
- 在遍歷完成之后,有一個最終的誊稚,時間很短的的Stop The World階段翔始,這個階段處理一些邊緣情況(我們現(xiàn)在將它忽略),該階段完成之后標(biāo)記階段就完成了里伯。