jvm知識(shí)點(diǎn)整理

7fa016dea26098cb7ab0b6ec35f04fb.png

內(nèi)存劃分

java虛擬機(jī)\color{red}{在執(zhí)行java程序的過(guò)程中}會(huì)把它所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域

image.png

程序計(jì)數(shù)器
當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。字節(jié)碼解釋器通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令
虛擬機(jī)棧
java方法執(zhí)行的內(nèi)存模型,它的生命周期與線程相同。方法執(zhí)行時(shí)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表(存放了各種基本數(shù)據(jù)類型boolean、byte、char、short、int、float、long、double,對(duì)象引用)、操作棧、動(dòng)態(tài)鏈接、方法出口等信息。
本地方法棧
虛擬機(jī)使用到的nativie方法服務(wù)
Java堆
存放對(duì)象的實(shí)例,幾乎所有對(duì)象都在這里分配內(nèi)存。棧上分配、標(biāo)量替換會(huì)導(dǎo)致其他情況
方法區(qū)
用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)
運(yùn)行時(shí)常量池,Class文件中除了類的版本、字段、方法、接口等之外還有常量池。用于存放編譯期生成的各種字面量和符號(hào)引用
1.8之后叫元空間
運(yùn)行時(shí)常量池
用于存放編譯期生成的各種字面量和符號(hào)引用,String類的intern()

虛擬機(jī)對(duì)象

對(duì)象的創(chuàng)建

1.類加載檢查
虛擬機(jī)遇到一條new指令,會(huì)首先進(jìn)行類加載的檢查:
--檢查這個(gè)指令的參數(shù)是否在常量池中定位到一個(gè)符號(hào)引用
--檢查該類是否被加載、解析和初始化,如果沒(méi)有就進(jìn)行類加載。
2.分配內(nèi)存
對(duì)象所需內(nèi)存的大小在類加載完成后便可完全確定,于是分配空間等同于把一塊確定大小的內(nèi)存從堆中劃分出來(lái),具體分配方式取決于堆內(nèi)存是否規(guī)整。是否規(guī)整是垃圾收集器是否帶有壓縮整理功能決定的。
-- 指針碰撞
堆內(nèi)存規(guī)整,移動(dòng)指針
-- 空閑列表
堆內(nèi)存不規(guī)整,虛擬機(jī)維護(hù)記錄可用內(nèi)存的列表,從列表中查找對(duì)應(yīng)對(duì)象大小的內(nèi)存區(qū)域分配給對(duì)象,并更新空閑列表。
為保證內(nèi)存分配時(shí)并發(fā)情況下線程安全問(wèn)題,有兩種方案:
-- CAS+失敗重試
虛擬機(jī)采用CAS配上失敗重試的方式保證更新操作的原子性來(lái)對(duì)分配內(nèi)存空間的動(dòng)作進(jìn)行同步處理
-- TLAB
把內(nèi)存分配的動(dòng)作按照線程劃分在不同的空間之中進(jìn)行,即每個(gè)線程在java堆中預(yù)先分配一小塊內(nèi)存

3.初始化
內(nèi)存分配完成后,虛擬機(jī)需要將除對(duì)象頭以外分配到的內(nèi)存空間都初始化為0,如果使用TLAB,這一工作過(guò)程也可以提前至TLAB分配時(shí)進(jìn)行。這一步操作保證了對(duì)象的實(shí)例字段在java代碼中可以不賦初始值就直接使用,程序能訪問(wèn)到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。

4.設(shè)置對(duì)象頭
初始化零值之后,虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,這些設(shè)置信息存放在對(duì)象的對(duì)象頭Object Header之中。
對(duì)象頭包括:markword、類型指針、數(shù)組長(zhǎng)度

5.執(zhí)行init方法
為對(duì)象屬性復(fù)制,執(zhí)行對(duì)象的構(gòu)造方法

對(duì)象的內(nèi)存布局

對(duì)象訪問(wèn)(對(duì)象定位)

1.使用句柄
句柄中包含了對(duì)象實(shí)例數(shù)據(jù)和類型數(shù)據(jù)各自的具體地址信息


通過(guò)句柄訪問(wèn)對(duì)象

2.直接指針


通過(guò)直接指針訪問(wèn)對(duì)象

分配對(duì)象

Java 中\color{red}{對(duì)象地址操作}主要使用 Unsafe 調(diào)用了 C 的 allocate 和 free 兩個(gè)方法,分配方法有兩種:
1.指針碰撞: 始終跟蹤上一次分配對(duì)象時(shí)使用的空間末尾地址。當(dāng)要為新對(duì)象分配空間時(shí),會(huì)在當(dāng)前所處的代的各個(gè)內(nèi)存塊中查找是否有適合大小的空間來(lái)分配給新對(duì)象,若有,則更新指針,初始化對(duì)象

2.空閑列表: 維護(hù)一個(gè)列表,記錄上哪些內(nèi)存可用。

垃圾對(duì)象

1.引用計(jì)數(shù)算法

給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值加1;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減1;任何時(shí)刻計(jì)數(shù)器都為0的對(duì)象就是不可能在被使用
缺點(diǎn)就是相互引用

2.根搜索算法

通過(guò)“GC Roots”的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索所走過(guò)的路勁成為引用鏈,當(dāng)一個(gè)對(duì)象到Gc Roots 沒(méi)有任何引用鏈相連,則證明此對(duì)象是不可用的。
Gc Roots對(duì)象包括:

  • 虛擬機(jī)棧(棧幀中的本地變量表)中的引用的對(duì)象
  • 方法區(qū)中的類靜態(tài)屬性引用的對(duì)象
  • 方法區(qū)中的常量引用的對(duì)象
  • 本地方法棧中JNI的引用的對(duì)象
    什么是引用?
    強(qiáng)引用
    只要強(qiáng)引用還在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象
    軟引用
    在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對(duì)象列進(jìn)回收范圍之中并進(jìn)行第二次回收
    弱引用
    只能生存到下一次來(lái)及收集發(fā)生之前,WeakReference類來(lái)實(shí)現(xiàn)弱引用
    虛引用

兩次標(biāo)記過(guò)程才會(huì)真正死亡。
第一次標(biāo)記:
判斷是否存在引用鏈。然后篩選(判斷是否有必要執(zhí)行finalize方法),并放置F-Queue的隊(duì)列中
第二次標(biāo)記
GC將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模的標(biāo)記

垃圾收集算法

1.標(biāo)記-清除算法
首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收掉所有被標(biāo)記的對(duì)象
缺點(diǎn): 1.效率問(wèn)題。2.空間問(wèn)題
2.復(fù)制算法
將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊用完了。就將存貨著的對(duì)象復(fù)制到另外一塊上。然后將已使用過(guò)的內(nèi)存空間一次清理掉。

3.標(biāo)記-整理算法
讓所有存貨的對(duì)象都向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存

3.分代收集算法
根據(jù)對(duì)象的存貨周期的不同將內(nèi)存劃分為幾塊。一般是把java堆分為新生代和老年代。這樣可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?/p>

垃圾收集器

image.png

如果兩個(gè)收集器之間存在連線,說(shuō)明可以搭配使用。
Serial收集器
jdk1.3.1之前新生代,單線程的收集器。收集垃圾時(shí),必須暫停其他所有的工作線程
優(yōu)點(diǎn):簡(jiǎn)單而高效,適合client端

ParNew收集器
Serial收集器的多線程版本

Parallel Scavenge收集器
新生代收集器,使用復(fù)制算法并行的多線程收集器。目標(biāo)是達(dá)到一個(gè)可控制的吞吐量
停頓時(shí)間越短就越適合需要與用戶交互的程序,良好的響應(yīng)速度能提升用戶的體驗(yàn)﹔而高吞吐量則可以最高效率地利用CPU時(shí)間,盡快地完成程序的運(yùn)算任務(wù),主要適合在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)。

Serial Old收集器
老年代版本的單線程收集器。使用“標(biāo)記-整理”算法

Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標(biāo)記-整理”算法。

CMS收集器
是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器?!皹?biāo)記-清除”

image.png

優(yōu)點(diǎn):并發(fā)收集、低停頓
缺陷:對(duì)cpu資源非常敏感、無(wú)法處理浮動(dòng)垃圾、產(chǎn)生大量空間碎片

G1收集器(精準(zhǔn)控制停頓時(shí)間,避免垃圾碎片)
減少“Stop the world”G1垃圾收集器能同時(shí)回收年輕代和老年代的對(duì)象,它最大的一個(gè)特點(diǎn)是把java堆內(nèi)存拆分為多個(gè)大小相等的region,使得每個(gè)小空間可以單獨(dú)進(jìn)行垃圾回收。在指定的時(shí)間范圍內(nèi),同時(shí)在有限的時(shí)間內(nèi)盡量回收盡可能多的垃圾對(duì)象
維護(hù)了一個(gè)優(yōu)先列表,根據(jù)允許的收集時(shí)間,優(yōu)先回收最大的region
G1 收集器兩個(gè)最突出的改進(jìn)是:

【1】基于標(biāo)記-整理算法,不產(chǎn)生內(nèi)存碎片。

【2】可以非常精確控制停頓時(shí)間,在不犧牲吞吐量前提下,實(shí)現(xiàn)低停頓垃圾回收。(增量回收,不是全量回收)

存放類的信息到元空間

JVM分代模型:年輕代和老年代

對(duì)象的分配機(jī)制

分配機(jī)制

正常情況下是在堆上分配

  • 新生代回收之后,因?yàn)榇婊顚?duì)象太多,導(dǎo)致大量對(duì)象直接進(jìn)入老年代

  • 特別大的超大對(duì)象直接不經(jīng)過(guò)新生代就進(jìn)入老年代
    有一個(gè)JVM參數(shù),就是“-XX:PretenureSizeThreshold”,可以把他的值設(shè)置為字節(jié)數(shù)。只要對(duì)象大于這個(gè)參數(shù),那么會(huì)直接進(jìn)入老年代。避免對(duì)象在survivor 對(duì)象間頻繁復(fù)制

  • 長(zhǎng)期存活的對(duì)象
    虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡計(jì)數(shù)器,經(jīng)過(guò)一次minorGC后仍然存活,并且被Survivor容納的話,就加一歲,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲),就將晉升到老年代。
    年齡閾值可通過(guò)“-XX:MaxTenuringThreshold”設(shè)置

  • 動(dòng)態(tài)對(duì)象年齡判斷機(jī)制
    年齡1+年齡2+年齡n的多個(gè)年齡對(duì)象總和超過(guò)了Survivor區(qū)域的50%,此時(shí)就會(huì)把年齡n以上的對(duì)象都放入老年代

  • 空間擔(dān)保機(jī)制
    每一次minor GC 之前,jvm會(huì)檢查一下老年代可用內(nèi)存空間,是否大于新生代所有對(duì)象的總大小,假如老年代的內(nèi)存小于新生代的全部對(duì)象大小,會(huì)看“-XX:-HandlePromotionFailure”的參數(shù)是否設(shè)置了,如果設(shè)置了就判斷老年代的內(nèi)存是否大于之前每一次minor Gc后進(jìn)入老年代的平均對(duì)象大小。如果小于或者沒(méi)有這個(gè)參數(shù),那么就會(huì)執(zhí)行full GC。如果執(zhí)行后還是沒(méi)有足夠的大小夠放對(duì)象。那么就會(huì)報(bào)“OOM”

方法區(qū)內(nèi)會(huì)不會(huì)進(jìn)行垃圾回收?

首先該類的所有實(shí)例對(duì)象都已經(jīng)從Java堆內(nèi)存里被回收
其次加載這個(gè)類的ClassLoader已經(jīng)被回收
最后,對(duì)該類的Class對(duì)象沒(méi)有任何引用

老年代什么是否觸發(fā)垃圾回收?

JVM 優(yōu)化之常用參數(shù)
-Xms: 堆最小空間,建議設(shè)置為物理內(nèi)存的1/4
-Xmx:堆最大空間,建議設(shè)置為物理內(nèi)存的1/2
-XX:NewRatio: Old/New的比例
-Xmn: 年輕代大小,調(diào)整會(huì)影響老年代大小,官方建議堆大小的3/8
-XX:SurvivorRatio: 調(diào)整Survivor和Eden的比例大小
-XX:MetaspaceSize: 元空間初始化大小,
-XX:MaxMetaspaceSize: 元空間最大大小
-XX:PretenureSizeThreshold: 大對(duì)象直接進(jìn)入老年代的閾值
-XX:MaxTenuringThreshold: 進(jìn)入老年代的分代年齡閾值

JVM性能調(diào)優(yōu)

常用命令:jps、jinfo、jstat、jstack、jmap

調(diào)測(cè)工具
Arthas

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容