1陈症、JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)
-
堆(共享)
- 堆用來存放對象和數(shù)組扰付,只要是堆中的對象,就可以被所有線程共享(靜態(tài)變量栅受、靜態(tài)常量、字符串存儲在堆中的老年代里)
- Java7 版本中將永久代的靜態(tài)變量和運(yùn)行時(shí)常量池轉(zhuǎn)移到堆中存放的
- 堆是 JVM 上最大的內(nèi)存區(qū)域担扑。垃圾回收操作的對象就是堆
- 堆空間一般是程序啟動(dòng)時(shí)就申請了,一般設(shè)置成可伸縮的。 隨著對象的頻繁創(chuàng)建浑塞,堆空間占用的越來越多,就需要不定期的對不再使用的對象進(jìn)行回收政己,這就是GC
- 對于基本數(shù)據(jù)類型對象(如byte酌壕、short、int歇由、long卵牍、float、double沦泌、char)糊昙,在方法體內(nèi)聲明時(shí),會直接分配在棧中,其它情況都會分配在堆中
- 對于普通對象來說谢谦,JVM 會首先在堆上創(chuàng)建對象释牺,然后在其他地方使用它的引用
- 比如,把這個(gè)引用保存在虛擬機(jī)棧的局部變量表中回挽。但是在開啟了逃逸分析時(shí)没咙,如果發(fā)現(xiàn)某個(gè)對象只會在方法內(nèi)部使用,則可能會將該對象經(jīng)過標(biāo)量替換后也存在棧中
- 堆的幾個(gè)重要參數(shù)
- -Xms:堆的最小值(初始值千劈,默認(rèn)單位是:字節(jié)祭刚,要求是1024的整數(shù)倍)
- -Xmx:堆的最大值
- -Xmn:新生代的大小
- -XX:NewSize;新生代最小值(初始值)
- -XX:MaxNewSize:新生代最大值
- 堆用來存放對象和數(shù)組扰付,只要是堆中的對象,就可以被所有線程共享(靜態(tài)變量栅受、靜態(tài)常量、字符串存儲在堆中的老年代里)
-
方法區(qū)(元空間)(共享)
- 方法區(qū)存儲的內(nèi)容
- 類型信息(比如類全稱队塘、父類全稱)
- 域信息(域名稱袁梗、域修飾符private等)
- 方法信息(方法名稱、方法修飾符憔古、返回類型等)
- 字面量(字面量包括文本字符串遮怜、八種基本類型的值 、被聲明為final的常量等)
- 假如兩個(gè)線程都試圖訪問方法區(qū)中的同一個(gè)類信息鸿市,而這個(gè)類還沒有加載進(jìn) JVM锯梁,那么此時(shí)就只允許一個(gè)線程去加載它,另一個(gè)線程必須等待
- 方法區(qū)是 JVM 對內(nèi)存的邏輯劃分
- 在 JDK1.7 及之前將方法區(qū)稱為永久代
- 在 JDK1.8 及以后使用了元空間來實(shí)現(xiàn)方法區(qū)
- 元空間大小參數(shù)設(shè)置:
- 如果不設(shè)置參數(shù)焰情,則只受本機(jī)總內(nèi)存的限制
- jdk1.7 及以前
- -XX:PermSize
- -XX:MaxPermSize
- jdk1.8 以后
- -XX:MetaspaceSize
- -XX:MaxMetaspaceSize
- 方法區(qū)存儲的內(nèi)容
-
本地方法棧
- 如果當(dāng)前線程執(zhí)行的方法是Native類型的陌凳,這些方法就會在本地方法棧中執(zhí)行
-
虛擬機(jī)棧(線程私有)
- Java虛擬機(jī)棧是當(dāng)前線程在執(zhí)行方法時(shí)存儲所需的數(shù)據(jù)、指令内舟、返回地址的一種棧結(jié)構(gòu)(先進(jìn)后出)合敦。它的生命周期與線程保持一致
- 每調(diào)用一個(gè)方法就會在棧里加入一個(gè)棧幀。調(diào)用的方法執(zhí)行完了验游,對應(yīng)的棧幀就會出棧
- 棧幀里分為4個(gè)區(qū)域充岛,這4個(gè)區(qū)域就包含了執(zhí)行Java方法時(shí)的全部內(nèi)容
- 局部變量表
- 存方法參數(shù)和局部變量保檐。如果是引用類型則存的是對象在堆中的地址。第0行崔梗,放的就是this
- 操作數(shù)棧
- 操作數(shù)棧也是一個(gè)先進(jìn)后出的棧結(jié)構(gòu)夜只,它用來臨時(shí)存放即將要操作的數(shù)據(jù)
- 動(dòng)態(tài)連接
- Java 語言特性多態(tài)(需要類加載、運(yùn)行時(shí)才能確定具體的方法)蒜魄,如子類重寫父類方法后扔亥,具體調(diào)用哪個(gè)要等到執(zhí)行時(shí)才能確定。這時(shí)候的符號引用轉(zhuǎn)化為直接引用稱為動(dòng)態(tài)鏈接
- 方法出口
- 方法出口其實(shí)就是記錄的返回地址谈为。例如:方法1調(diào)用了方法2之后旅挤,那么方法2的出口地址就是方法1調(diào)用方法2的代碼位置,因?yàn)樵诜椒?執(zhí)行完了之后伞鲫,要回到方法1繼續(xù)執(zhí)行谦铃。
- 正常返回(遇到方法返回的字節(jié)碼指令)
- 異常返回(通過異常處理器表<非棧幀中的>來確定),并且這個(gè)異常沒有在方法體內(nèi)得到處理
- 局部變量表
-
程序計(jì)數(shù)器(線程私有)
- 由于現(xiàn)在都是多線程運(yùn)行榔昔,而一個(gè)CPU在同一時(shí)刻只能運(yùn)行一個(gè)線程,多個(gè)線程只能交替運(yùn)行瘪菌。程序計(jì)數(shù)器的作用就是記錄當(dāng)前線程下一條要運(yùn)行的指令撒会,這樣保證了線程在切換回來時(shí)能回到正確的位置繼續(xù)開始執(zhí)行
- 如果正在執(zhí)行的是Native 方法,由于不是JVM執(zhí)行师妙,則這個(gè)計(jì)數(shù)器值為空(Undefined)
1.1诵肛、堆空間劃分
-
一般情況下,新創(chuàng)建的對象都會被分配到Eden區(qū)默穴,一些特殊的大的對象會直接分配到Old區(qū)
- 大對象直接進(jìn)入老年代
- JVM參數(shù)
-XX:PretenureSizeThreshold
來設(shè)置對象大小閾值(字節(jié)數(shù))- 比如“1048576”怔檩,就是1M
- JVM參數(shù)
- 經(jīng)過15次GC仍然存活的對象
- JVM參數(shù)
-XX:MaxTenuringThreshold
來設(shè)置存活次數(shù),默認(rèn)15
- JVM參數(shù)
- 動(dòng)態(tài)對象年齡判斷
- Survivor區(qū)域里一批對象的 總大小大于了這塊Survivor區(qū)域的內(nèi)存大小的50% 蓄诽,那么此時(shí)大于等于這批對象年齡的對象薛训,就可以直接進(jìn)入老年代了
- Survivor區(qū)空間不夠
- GC后Survivor區(qū)空間不夠存放對象了,這時(shí)會吧對象直接轉(zhuǎn)移到老年代
- 老年代空間分配擔(dān)保規(guī)則
- 大對象直接進(jìn)入老年代
-
對象入堆流程
image-20230301160753966.png
1.2仑氛、Java對象內(nèi)存布局
-
對象頭
- Mark Word
- 包含一系列的標(biāo)識乙埃,例如鎖的標(biāo)記、對象年齡等锯岖。在32位系統(tǒng)占4字節(jié)介袜,在64位系統(tǒng)中占8字節(jié)
- Class Pointer
- 指向?qū)ο笏鶎俚?Class 在方法區(qū)的內(nèi)存指針,通常在32位系統(tǒng)占4字節(jié)出吹,在64位系統(tǒng)中占8字節(jié)遇伞,64位 JVM 在 1.6 版本后默認(rèn)開啟了壓縮指針,那就占用4個(gè)字節(jié)
- Length
- 如果對象是數(shù)組捶牢,還需要一個(gè)保存數(shù)組長度的空間鸠珠,占 4 個(gè)字節(jié)
- Mark Word
-
Mark Word
image-20230302140421111.png -
Monitor
- Java虛擬機(jī)給每個(gè)對象和class字節(jié)碼都設(shè)置了一個(gè)監(jiān)聽器Monitor巍耗,用于檢測并發(fā)代碼的重入
- 當(dāng)多個(gè)線程同時(shí)請求某個(gè)對象監(jiān)視器時(shí),對象監(jiān)視器會設(shè)置幾種狀態(tài)用來區(qū)分請求的線程
- Contention List:所有請求鎖的線程將被首先放置到該競爭隊(duì)列
- Entry List:Contention List中那些有資格成為候選人的線程被移到Entry List
- Wait Set:那些調(diào)用wait方法被阻塞的線程被放置到Wait Set
- OnDeck:任何時(shí)刻最多只能有一個(gè)線程正在競爭鎖跳芳,該線程稱為OnDeck
- Owner:獲得鎖的線程稱為Owner
- !Owner:釋放鎖的線程
2芍锦、垃圾回收(GC)
-
如何判斷垃圾對象
- 可達(dá)性分析算法
- 是以根對象(GCRoots)為起始點(diǎn),按照從上到下的方式搜索被根對象集合所連接的目標(biāo)對象是否可達(dá)
- GC Roots包括
- 本地方法棧內(nèi)JNI,引用的對象
- 方法區(qū)中靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
- 所有被同步鎖synchronized持有的對象
- Java虛擬機(jī)內(nèi)部的引用
- GC Roots包括
- 使用可達(dá)性分析算法后飞盆,內(nèi)存中存活的對象都被被根對象集合直接或間接連接著娄琉,搜索所走過的路徑稱為引用鏈
- 如果目標(biāo)對象沒有任何引用鏈相連,則是不可達(dá)的吓歇,意味著該對象已經(jīng)死亡孽水,可以標(biāo)記為垃圾對象
- 是以根對象(GCRoots)為起始點(diǎn),按照從上到下的方式搜索被根對象集合所連接的目標(biāo)對象是否可達(dá)
- 可達(dá)性分析算法
-
GC算法
- 標(biāo)記-清除算法
- 從引用根節(jié)點(diǎn)開始遍歷,標(biāo)記所有被引用的對象城看,一般是在對象Header中記錄為可達(dá)對象
- 對堆內(nèi)存從頭到尾進(jìn)行線性的遍歷女气,如果發(fā)現(xiàn)某個(gè)對象在其Header中沒有標(biāo)記為可達(dá)對象,則將其回收
- 復(fù)制算法
- 將或者的內(nèi)存空間分為兩塊测柠,每次使用其中一塊炼鞠。在垃圾回收時(shí),將正在使用的內(nèi)存中的存活的對象復(fù)制到未被使用的內(nèi)存塊中轰胁,之后清除正在使用的內(nèi)存塊中的所有的對象谒主,交換兩個(gè)內(nèi)存的角色,最后完成垃圾回收
- 標(biāo)記-壓縮(標(biāo)記-整理)算法
- 第一個(gè)階段和標(biāo)記清除算法一樣赃阀,從根節(jié)點(diǎn)開始標(biāo)記所有被引用的對象
- 第二階段將所有的存活對象壓縮在內(nèi)存的一端霎肯,按照順序排放,之后清理邊界外所有的空間
- 最終效果等同于標(biāo)記清除算法執(zhí)行完成后榛斯,再進(jìn)行一次內(nèi)存碎片整理
- 與標(biāo)記清除算法本質(zhì)區(qū)別观游,標(biāo)記清除算法是非移動(dòng)式的算法,標(biāo)記壓縮是移動(dòng)式的
- 分代收集算法
- 不同生命周期的對象可以采取不同的收集方式驮俗,以便提高回收效率懂缕,幾乎所有的GC都采用分代收集算法執(zhí)行垃圾回收的
- 年輕代
- 生命周期短,存活率低意述,回收頻繁
- 采用Serial提佣、ParNew、Parallel Scavenge回收算法
- 老年代
- 區(qū)域較大荤崇,生命周期長拌屏,存活率高,回收不及年輕代頻繁
- 采用Serial Old术荤、Parallel Old GC倚喂、CMS 回收算法
- 增量收集算法
- 每次垃圾收集線程只收集一小片區(qū)域的內(nèi)存空間,接著切換到應(yīng)用程序線程,依次反復(fù)端圈,直到垃圾收集完成
- 通過對線程間沖突的妥善管理焦读,允許垃圾收集線程以分階段的方式完成標(biāo)記、清理或復(fù)制工作
- 分區(qū)算法
- 為了控制GC產(chǎn)生的停頓時(shí)間舱权,將一塊大的內(nèi)存區(qū)域分割成多個(gè)小塊矗晃,根據(jù)目標(biāo)的停頓時(shí)間,每次合理的回收若干個(gè)小區(qū)間宴倍,而不是整個(gè)堆空間张症,從而減少一次GC所需時(shí)間
- 分代算法是將對象按照生命周期長短劃分為兩個(gè)部分,分區(qū)算法是將整個(gè)堆劃分為連續(xù)的不同的小區(qū)間
- 每一個(gè)小區(qū)間都獨(dú)立使用鸵贬,獨(dú)立回收俗他,這種算法的好處是可以控制一次回收多少個(gè)小區(qū)間
- 標(biāo)記-清除算法
-
Serial回收器(串行回收)
- Serial收集器采用復(fù)制算法,串行回收和STW機(jī)制的方式執(zhí)行內(nèi)存回收
-
ParNew回收器(并行回收)
- 除了采用并行回收阔逼,其他方面和Serial之間幾乎沒有任何區(qū)別
- 年輕代使用兆衅,不影響老年代
-
CMS回收器(低延遲)
CMS(Concurrent Mark Sweep)收集器是一種以獲取
最短回收停頓時(shí)間
為目標(biāo)的收集器-
采用的是
標(biāo)記-清除算法
整個(gè)過程分為4步- 初始標(biāo)記
- 標(biāo)記GC Roots能關(guān)聯(lián)到的對象,需要Stop The World
- 并發(fā)標(biāo)記
- 進(jìn)行GC Roots Tracing
- 重新標(biāo)記
- 修改并發(fā)標(biāo)記因用戶程序變動(dòng)的內(nèi)容嗜浮,需要Stop The World
- 并發(fā)清除
- 初始標(biāo)記
-
由于整個(gè)過程中羡亩,并發(fā)標(biāo)記和并發(fā)清除,收集器線程可以與用戶線程一起工作危融,所以總體上來
說夕春,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)地執(zhí)行的
由于在垃圾收集階段用戶線程沒有中斷,所以在CMS回收過程中专挪,還應(yīng)該確保應(yīng)用程序用戶線程有足夠的內(nèi)存可用。因此CMS收集器不能像其他收集器那樣等到老年代幾乎填滿再進(jìn)行回收片排,而是當(dāng)堆內(nèi)存使用率達(dá)到某一閾值時(shí)寨腔,便開始進(jìn)行回收
CMS采取標(biāo)記清除算法,會產(chǎn)生內(nèi)存碎片率寡,只能夠選擇空閑列表執(zhí)行內(nèi)存分配
-
G1回收器
- Java堆的內(nèi)存布局與就與其他收集器有很大差別迫卢,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),雖然還保留有新生代和老年代的概念冶共,但新生代和老年代不再是物理隔離的了乾蛤,它們都是一部分Region(不需要連續(xù))的集合
- 使用不同的region表示Eden,s0捅僵,s1家卖,老年代等,G1跟蹤各個(gè)region里面垃圾堆積的價(jià)值大小庙楚,在后臺維護(hù)一個(gè)優(yōu)先列表上荡,每次根據(jù)允許的收集時(shí)間,優(yōu)先回收價(jià)值最大的Region
- 優(yōu)勢
- 并行與并發(fā)
- 同時(shí)兼顧年輕代與老年代
- 空間整合
- region之間用復(fù)制算法馒闷,整體可以看做是標(biāo)記壓縮算法酪捡。
- 兩種算法都避免內(nèi)存碎片叁征,有利于程序長時(shí)間運(yùn)行,分配大對象不會因?yàn)闊o法找到連續(xù)空間提前觸發(fā)下一次GC逛薇,尤其當(dāng)Java堆非常大的時(shí)候捺疼,G1優(yōu)勢更加明顯
- 可預(yù)測的停頓時(shí)間模型
- 能讓使用者明確指定在一個(gè)長度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不能超過N毫秒
- Region
- region可以充當(dāng)多個(gè)角色永罚,所有region大小相同啤呼,且在JVM生命周期內(nèi)不會改變
- 一個(gè)region可能屬于Eden、survivor或Old內(nèi)存區(qū)域尤蛮。但是一個(gè)region只能屬于一個(gè)角色
- G1還增加了一個(gè)新的內(nèi)存區(qū)域:humongous媳友,主要用于存儲大對象,如果超過1.5個(gè)region产捞,就放到humongous中
- 工作過程可以分為如下幾步
- 初始標(biāo)記
- 標(biāo)記一下GC Roots能夠關(guān)聯(lián)的對象醇锚,并且修改TAMS的值,需要Stop The World
- 并發(fā)標(biāo)記
- 從GC Roots進(jìn)行可達(dá)性分析坯临,找出存活的對象焊唬,與用戶線程并發(fā)執(zhí)行
- 最終標(biāo)記
- 修正在并發(fā)標(biāo)記階段因?yàn)橛脩舫绦虻牟l(fā)執(zhí)行導(dǎo)致變動(dòng)的數(shù)據(jù),需要Stop The World
- 篩選回收
- 對各個(gè)Region的回收價(jià)值和成本進(jìn)行排序看靠,根據(jù)用戶所期望的GC停頓時(shí)間制定回收計(jì)劃
- 初始標(biāo)記
3赶促、什么是JMM
JMM就是Java內(nèi)存模型(java memory model)。JMM 是建立在硬件內(nèi)存模型基礎(chǔ)上的抽象模型挟炬,因?yàn)樵诓煌挠布a(chǎn)商和不同的操作系統(tǒng)下鸥滨,內(nèi)存的訪問有一定的差異,所以JMM屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異谤祖,以實(shí)現(xiàn)讓java程序在各種平臺下都能達(dá)到一致的并發(fā)效果
-
JMM抽象結(jié)構(gòu)劃分為線程本地緩存與主存婿滓,每個(gè)線程均有自己的本地緩存,本地緩存是線程私有的粥喜,主存則是計(jì)算機(jī)內(nèi)存凸主,它是共享的
932dc2d271410523a126591b8268cb74.png
- JMM內(nèi)存模型規(guī)則
- 我們所有定義的變量(除局部變量)都存放在主內(nèi)存(Main Memory)
- 每個(gè)線程都有自己的工作內(nèi)存,工作內(nèi)存中存放需要使用的變量的主內(nèi)存副本
- 線程對所有變量的操作都必須在工作內(nèi)存中進(jìn)行额湘。不能直接在主內(nèi)存中操作
- 不同線程之間無法直接訪問對方工作內(nèi)存中的變量卿吐。線程間變量值的傳遞需要通過主內(nèi)存進(jìn)行傳遞
- 保證并發(fā)的三大特性
- 可見性
- 當(dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改锋华,這就是可見性嗡官,如果無法保證,就會出現(xiàn)緩存一致性的問題
-
JMM
規(guī)定毯焕,所有的變量都放在主存中谨湘,當(dāng)線程使用變量時(shí),先從緩存中獲取,緩存未命中紧阔,再從主存復(fù)制到緩存坊罢,最終導(dǎo)致線程操作的都是自己緩存中的變量 - Java是利用
volatile
關(guān)鍵字來提供可見性的。 當(dāng)變量被volatile修飾時(shí)擅耽,這個(gè)變量被修改后會立刻刷新到主內(nèi)存活孩,并失效其他線程的緩存,當(dāng)其它線程需要讀取該變量時(shí)乖仇,會去主內(nèi)存中讀取新值
- 原子性
-
原子性是指一個(gè)或者多個(gè)操作在
CPU
執(zhí)行的過程中不被中斷的特性憾儒,一個(gè)線程在執(zhí)行時(shí)不會被其他線程干擾 -
Java
中提供了synchronized
(同時(shí)滿足有序性、原子性乃沙、可見性)可以保證結(jié)果的原子性
-
原子性是指一個(gè)或者多個(gè)操作在
- 有序性
- 編譯器和處理器為了優(yōu)化性能起趾,會對代碼做重排,所以語句實(shí)際執(zhí)行的先后順序與輸入的代碼順序可能一致警儒,這就是指令重排序
- 為解決重排序训裆,使用Java提供的
volatile
修飾變量同時(shí)保證可見性、有序性蜀铲,被volatile
修飾的變量會加上內(nèi)存屏障禁止排序
- 可見性
特性 | volatile | synchronized | Lock | Atomic |
---|---|---|---|---|
可見性 | 可以保證 | 可以保證 | 可以保證 | 可以保證 |
原子性 | 無法保證 | 可以保證 | 可以保證 | 可以保證 |
有序性 | 一定程度保證 | 可以保證 | 可以保證 | 無法保證 |