JVM

JVM組成

jvm由類加載器+內(nèi)存+執(zhí)行引擎

JVM內(nèi)存區(qū)域

  • 堆 線程共享 存儲對象
  • 虛擬機棧 線程私有,生命周期跟隨線程
  • 棧幀 虛擬機棧中元素;局部變量表,操作數(shù)棧(做運算),動態(tài)鏈接(指向常量池中方法引用),方法出口
  • 本地方法棧 線程私有
  • 程序計數(shù)器 線程私有,無oom
  • 方法區(qū) 1.7之前是永久代、1.8之后是元空間
  • 直接內(nèi)存

對象內(nèi)存布局OOP

對象頭 markword 8字節(jié);klasspointer 4字節(jié)(開啟指針壓縮)/8字節(jié);數(shù)組長度
內(nèi)部引用
對齊

為什么要對齊

指針對齊指的是,對象的大小要是8字節(jié)的整倍數(shù),如果不足會使用空位不足
應(yīng)該是為了尋址更方便,可能跟指針壓縮有關(guān),指針壓縮的原理就是將原地址/8,使用更短的地址表示

指針壓縮原理

64位地址分為堆的基地址+偏移量,當(dāng)堆內(nèi)存<32GB時候,在壓縮過程中,把偏移量/8后保存到32位地址。在解壓再把32位地址放大8倍,所以啟用CompressedOops的條件是堆內(nèi)存要在4GB*8=32GB以內(nèi)。
CompressedOops,可以讓跑在64位平臺下的JVM,不需要因為更寬的尋址,而付出Heap容量損失的代價。 不過它的實現(xiàn)方式是在機器碼中植入壓縮與解壓指令,可能會給JVM增加額外的開銷。

java中有哪些類加載器

BootstrapClassLoader啟動類加載器(非java實現(xiàn),無法獲取,無法操作),負責(zé)加載<Java_Home>/lib下面的核心類庫或-Xbootclasspath選項指定的jar包
ExtClassLoader擴展類加載器,負責(zé)加載<Java_Home>/lib/ext或者由系統(tǒng)變量-Djava.ext.dir指定位置中的類庫 Launcher靜態(tài)加載
AppClassLoader系統(tǒng)類加載器,負責(zé)加載系統(tǒng)類路徑-classpath或-Djava.class.path變量所指的目錄下的類庫 Launcher靜態(tài)加載
URLClassLoader,ext和app是他的子類,他實現(xiàn)了findClass方法,可以根據(jù)一組url找到對應(yīng)的文件
ThreadContextClassLoader線程上下文類加載器,這不是一個真正的類加載器,而是在項目啟動階段設(shè)置到Thread中的,子線程會繼承父線程線程上下文類加載器

java的類加載機制

全盤負責(zé) 誰觸發(fā)的加載,由誰的類加載器質(zhì)性加載
雙親委派 向上查找,向下加載;加載類時,先看自己有沒有,再看父加載器有沒有,都沒有,則從父加載器開始向下加載
緩存機制 加載一次之后的類將會被緩存在類加載器中

java類加載過程

裝載 將class文件找到,并加載方法區(qū)(1.8之后放在元空間中的 klassMetaSpace)
驗證 驗證class文件的格式、魔數(shù)、語義、語法
準備 為class的靜態(tài)變量分配內(nèi)存,并附默認值,int=0 boolean=false
解析 替換常量池中的符號引用為直接引用
初始化 為class的靜態(tài)變量賦值,執(zhí)行static塊方法
使用
銷毀

破壞雙親委派機制

指的是ThreadContextClassLoader可以在任何場景下被獲取并執(zhí)行類加載,本質(zhì)上其實破壞的是全盤負責(zé)機制,雙親委派還是完整的

java SPI機制,指的是java定義了一些核心接口比如JDBC,具體實現(xiàn)由各大廠商進行實現(xiàn),廠商將驅(qū)動啟動類配置在一個文件中,jvm啟動階段會讀取此文件執(zhí)行加載,而負責(zé)加載的類在核心類路徑上,根據(jù)全盤負責(zé)機制,應(yīng)該有啟動類加載器進行加載,那這肯定是加載不到的,所以會使用線程上線文類加載器
spring也使用的是線程上下文類加載器
線程上下文類加載器在啟動階段被設(shè)置為appclassloader

雙親委派的好處

1.唯一性,類只會被加載一次
2.安全性,核心類庫不會被篡改
3.隔離性,不同自定義類加載器的類不共享,但是可以共享父加載器的,參考tomcat實現(xiàn)隔離不同webwork

如何確定是否應(yīng)該回收對象

  1. 引用計數(shù)法 每個對象維護一個數(shù)字代表被多少對象引用,循環(huán)引用場景有問題
  2. 可達性分析 從gcroot出發(fā),能訪問到的對象代表不是垃圾

java中引用類型

強軟弱虛引用,軟引用在內(nèi)存不足時會被回收,弱引用在GC時會被回收,虛引用用來管理對外內(nèi)存

TLAB

Thread Local Allocation Buffer 防止線程競爭內(nèi)存地址,所以給每個線程分配一塊buffer

PLAB

Promotion Local Allocation Buffers 年輕代對象晉升時,申請的一塊老年代地址

GCRoot

虛擬機棧和本地方法棧中的局部變量持有的對象,方法區(qū)靜態(tài)變量和常量持有的對象

GC算法

  1. 標記-復(fù)制
  2. 標記-清除
  3. 標記-整理

三色標記法

完全掃描的對象標記為黑色,部分掃描的對象標記為灰色,未掃描的對象為白色;黑色對象在如果發(fā)生引用變化,會被標記為灰色

垃圾收集器

  • Serial 年輕代串行收集器 復(fù)制
  • SerialOld 老年代的串行收集器 整理
  • PS 年輕代并行收集器 復(fù)制 吞吐量優(yōu)先,可以設(shè)置吞吐量、最大停頓時間,開啟自適應(yīng)自動配置分區(qū)大小,1.8默認
  • PO 老年代并行收集器 整理
  • PN 年輕代并行收集器 復(fù)制 配合CMS
  • CMS 老年代并發(fā)收集器 清除 追求最小停頓,可以與用戶線程并發(fā)執(zhí)行,cup敏感,浮動垃圾,內(nèi)存碎片
  • G1 可以回收年輕代和老年代,復(fù)制/整理 追求最小停頓和吞吐量的平衡,弱化分代,使用分區(qū),內(nèi)存劃分為2000個區(qū)域,每個區(qū)域可能是s、e、o、p,可以開啟自適應(yīng)自動配置分區(qū)大小,1.9默認
  • ZGC 徹底摒棄分代回收 1.11

對象晉升老年代的幾種情況

  1. 分配擔(dān)保機制
  2. 達到年齡
  3. 大對象直接分配 需要配置 默認不開啟
  4. 動態(tài)年齡 當(dāng)s區(qū)小于某一個年齡的對象大小超過s區(qū)一半,大于等于此年齡的對象直接進入老年代

STW

stop the world在gc過程中,為了保證某些一致性,要強制暫停所有用戶線程

安全點和安全區(qū)域

安全點和安全區(qū)域指代碼中一些引用不會發(fā)生改變的地方,當(dāng)線程運行到這類位置時,堆對象狀態(tài)是確定一致的,JVM可以安全地進行操作,比如return之前、調(diào)用方法之后、拋出異常之前、循環(huán)的末尾;阻塞中(安全區(qū)域);
一些操作會觸發(fā)線程執(zhí)行到安全點后停頓,GC,偏向鎖接觸,JIT優(yōu)化等

CMS回收過程

1.初始標記 會導(dǎo)致STW,標記直接被GCRoots引用的對象
2.并發(fā)標記 與用戶線程并發(fā)執(zhí)行,使用三色標記法標記對象
3.并發(fā)預(yù)清理 清理用戶線程并發(fā)過程中產(chǎn)生的對象引用變化,對象引用變化會觸發(fā)寫屏障,將對應(yīng)的cardtable位置標記為dirty區(qū)域,
4.可中斷的并發(fā)預(yù)清理 重復(fù)執(zhí)行步驟3,盡可能的減少并發(fā)產(chǎn)生的臟頁,預(yù)期一次YGC,減少年輕代對象,可以通過參數(shù)配置,一般會在5s左右
5.重新標記 這個階段是 STW 的,因為并發(fā)階段引用關(guān)系會發(fā)生變化,所以要重新遍歷一遍新生代對象、Gc Roots、卡表、ModUnionTable等,來修正標記。
6.并發(fā)清除 清除垃圾對象
7.線程重置

CMS的問題

  • 浮動垃圾 在并發(fā)標記和并發(fā)預(yù)清理階段會產(chǎn)生浮動垃圾,就是本身已經(jīng)被標記為黑色的對象,在并發(fā)標記階段被用戶線程修改為沒有引用,本質(zhì)上應(yīng)該是白色的,但是由于無法觸達,所以一直是黑色的,會在下一次CMS被回收,浮動垃圾過多也會觸發(fā)fullgc,參數(shù)配置CMS觸發(fā)機制目前是75%
  • 內(nèi)存碎片 CMS使用清除算法,被清除的地方形成一個一個不連續(xù)空洞,這就是內(nèi)存碎片,內(nèi)存碎片會影響大對象的內(nèi)存分配,嚴重時會觸發(fā)SerialOld,強制進行內(nèi)存整理,這就是fullgc
  • promotion failed 進行ygc時,年輕代放不下,通過分配擔(dān)保機制,檢查老年代是否有足夠空間,如果沒有則拋出promotion failed,此時old區(qū)還沒有開始CMS,降級為Serail OldGC,主要是內(nèi)存隨碎片導(dǎo)致的
  • concurrent mode failure 在CMS執(zhí)行過程中,出現(xiàn)對象晉升失敗,拋出concurrent mode failure 降級為Serail OldGC

CMS中的Incremental update

cms通過incremental update write barrie 在并發(fā)標記期間,將發(fā)生引用變化的黑色對象編程灰色對象。

CMS中的cardtable

Hospot將老年代內(nèi)存按512字節(jié)劃分為一個一個card,而卡表就是一個字節(jié)數(shù)組,數(shù)組中每一個元素對應(yīng)一個card;CARD_TABLE[address>>9]=DIRTY
YGC需要卡表記錄老年代對象對年輕代對象的引用,用以規(guī)避全量掃描老年代對象;
CMS用卡表記錄老年代對象在并發(fā)標記過程中對象引用的變化

CMS中的ModUnionTable

ModUnionTable為了補充CMS和YGC同時發(fā)生時對cardtable的操作,YGC利用卡表記錄老年代對年輕代對象的引用,YGC掃描到這個card之后會重置,這樣CMS標記的dirty會丟失,所以使用ModUnionTable

觸發(fā)fullgc的情況

  • 擔(dān)保失敗promotion failed
  • CMS期間 concurrent mode failure

G1垃圾收集器

分區(qū)又分代,將內(nèi)存等分成2000多個region,每個region可以是Eden、S、Old、超大對象區(qū)(大小超過region的一半)
younggc和mixedgc模式
吞吐量和最小停頓權(quán)衡

Cset

一組需要被回收的region集合,YGC只包含e和s,MixedGC包含e、s和回收價值最大的一些o區(qū)

Rset

每個region維護一個remmberset,存儲引用了本region對象被其他region區(qū)對象的引用
points-into 和 points-out ;CMS的卡表是out,G1的rset是in

SATB

標記期間,一旦引用關(guān)系發(fā)生變化,將此引用記錄下來,就認為他是活的,Snapshot-at-beginning,從標記開始,認為可達的對象都是活的,即使并發(fā)過程中出現(xiàn)引用變化,產(chǎn)生浮動垃圾;
標記開始時生成兩個指針,一個pre一個next,這兩個指針區(qū)間的對象是標記期間創(chuàng)建的,視為活著的對象

G1回收過程

YGC
stw 從gcroots+rset出發(fā)掃描整個年輕代,此處是多線程并行完成,將存活對象copy到其他s區(qū),清除垃圾
存活對象會被復(fù)制或移動到一個或者更多的survivor區(qū)域。如果這個閾值被滿足了,其中的一些對象就會晉升到老年代中。
這里會出現(xiàn)STW的暫停。Eden和survivor的大小會被計算來供下一個年輕代的GC所使用。整體的信息會被保存起來來計算大小。暫停時間目標的這個事就會被考慮進來。
這種方式使得我們很容易的來調(diào)整區(qū)域的大小,使得它們根據(jù)實際需要變得更大或者更小。

MixedGG
global concurrent mark 全局并發(fā)標記

  • 初始標記 stw 標記gcroots 使用外部bitmap,不用對象頭 這里復(fù)用了ygc的stw
  • 并發(fā)階段 從gcroots和rset出發(fā) 三色標記,SATB開始工作
  • 最終標記 stw 處理SATB里的引用
  • 清理 stw 清理為空region 置為freelist
  • 復(fù)制 stw 將存活對象復(fù)制到其他的區(qū)域

G1主要運行模式

YGC
MixedGC
fullGC

YGC何時發(fā)生

新生代不夠用了

MisedGC何時發(fā)生

G1HeapWastePercent:在global concurrent marking結(jié)束之后,我們可以知道old gen regions中有多少空間要被回收,在每次YGC之后和再次發(fā)生Mixed GC之前,會檢查垃圾占比是否達到此參數(shù),只有達到了,下次才會發(fā)生Mixed GC。
G1MixedGCLiveThresholdPercent:old generation region中的存活對象的占比,只有在此參數(shù)之下,才會被選入CSet。
G1MixedGCCountTarget:一次global concurrent marking之后,最多執(zhí)行Mixed GC的次數(shù)。
G1OldCSetRegionThresholdPercent:一次Mixed GC中能被選入CSet的最多old generation region數(shù)量。

G1何時發(fā)生fullGC

當(dāng)晉升失敗、疏散失敗、大對象分配失敗、Evac失敗時,有可能觸發(fā)Full GC,在JDK10之前,F(xiàn)ull GC是串行的,JEP 307: Parallel Full GC for G1之后引入了并行Full GC。

G1其他參數(shù)

設(shè)置region大小
設(shè)置個
手機目標時間 默認200ms
新生代最小值 5%
新生代最大值 60%
并行GC線程數(shù)
并發(fā)標記階段并行線程數(shù)

JIT是什么

JIT即使編譯,是JVM虛擬機解釋引擎的一部分,我們知道java是一種先編譯后解釋的語言,在運行階段,由JVM將class文件中的程序解釋成機器語言來執(zhí)行,每次解釋還是會影響效率,JIT就是將一些熱點代碼直接翻譯成機器語言后進行緩存,下次執(zhí)行這個緩存就好了,在翻譯和緩存過程中,還可以對代碼進行一些動態(tài)的優(yōu)化,比如逃逸分析、標量替換、鎖消除、鎖粗化、方法內(nèi)聯(lián)等

JIT的client和server

jit有client和server模式,client模式啟動快,針對少量代碼進行即使編譯,適合客戶端程序;server模式會針對大量大嗎進行優(yōu)化,啟動慢,適合做服務(wù)器程序

如何確定那些代碼需要編譯

當(dāng) JVM 執(zhí)行一個 Java 方法,它會檢查方法調(diào)用計數(shù)器和回邊計數(shù)器的總和,以決定這個方法是否有資格被編譯。如果有,則這個方法將排隊等待編譯。這種編譯形式并沒有一個官方的名字,但是一般被叫做標準編譯。
client模式是1500 server模式是10000

何時編譯

異步,允許邊執(zhí)行邊編譯
棧上替換OSR,應(yīng)對長循環(huán)或者死循環(huán)的編譯,編譯完成之后,下一次循環(huán)直接替換為編譯好的機器碼

JIT還有哪些功能

逃逸分析、標量替換 對象棧上分配
鎖消除 對于永遠不會并發(fā)訪問的區(qū)域去除synconized
鎖粗化 對于循環(huán)內(nèi)部的synconized可能會遷移到外部
方法內(nèi)聯(lián) 將方法內(nèi)調(diào)用的其他方法直接納入本方法中,減少出戰(zhàn)入棧
profile優(yōu)化 如果這這方法一直只進行一個分支的判斷,虛擬機就會推斷,程序不會走另一個分支,即時編譯器就會省略掉一個,只對一個分支進行編譯為機器碼。

JVM調(diào)優(yōu)經(jīng)驗

  • cv優(yōu)化過程
    cv項目的oldgc優(yōu)化,cv的內(nèi)存使用本身具有一定的特色,在簡歷查詢場景,組裝一份簡歷會產(chǎn)生大量的臨時對象,簡歷對象本身存在一些文本信息,一份簡歷的平均長度在5000+char左右,在批量查詢場景中,一組簡歷被返回,如果有200份簡歷那么這個數(shù)據(jù)大概會有2m左右的一個集合對象,并且接口響應(yīng)較長,在一個長時間中內(nèi)存會持有這些簡歷對象,并且cv的qps很高,峰值會有1w的qps。cv項目盡管已經(jīng)加到15個實例,仍然每天伴會有幾次cms進行執(zhí)行

第一次 優(yōu)化cv的jvm的目的,其實就是降低cms執(zhí)行,cms盡管是低停頓收集器,但是回收過程中不免進行stw導(dǎo)致一些線程超時
針對cv的優(yōu)化從以下幾點入手,
1.使用寬表緩存,減少組裝簡歷過程中產(chǎn)生的臨時對象
2.限制接口size數(shù)量
3.調(diào)整jvm參數(shù),策略是適當(dāng)調(diào)大年輕代,也可以減少ygc的產(chǎn)生,降低因為年齡達到進入老年代,調(diào)整s區(qū)比例,減少因s區(qū)不足進入老年代的場景,這個過程是一個慢慢調(diào)試的過程
第一次優(yōu)化使cv已經(jīng)cms次數(shù)降低到了1周1次

第二次 優(yōu)化cv是為了進一步降低服務(wù)的停頓
1.發(fā)現(xiàn)日志中有大量的安全點stw日志,通過日志發(fā)現(xiàn)很多偏向鎖的釋放導(dǎo)致進入的stw,刪除代碼中的一些無用的synconized,這里其實無傷大雅,大部分都是框架產(chǎn)生的
2.cms的最終標記時間user time過長,了解到公司服務(wù)器最多允許使用3核,將cms并發(fā)度調(diào)整到了3核,之前是15,頻繁切換線程確實會影響

第三次 優(yōu)化cv是為了解決concurrent mode failure問題
配置了每執(zhí)行5次cms 執(zhí)行一次整理算法,現(xiàn)階段的cv已經(jīng)能做到1周1次cms

第四次 優(yōu)化cv是為了進一步減少服務(wù)壓力
將簡歷緩存封裝成輕量級接口,直接織入業(yè)務(wù)端代碼,以前的模式是RPC-》緩存,改成緩存-》RPC,減少服務(wù)壓力

  • user平臺優(yōu)化
    user平臺也十分具有特點,那就是qps超高,峰值能達到2w
    正常情況下,user平臺雖然qps很高,但是沒有響應(yīng)時間很長的那種接口,經(jīng)過調(diào)整jvm參數(shù),對象會很快被回收掉,每天仍然會有1次cms
    通過排查內(nèi)存快照,定位到某個內(nèi)部定時任務(wù)會生成超大對象集合,處理過之后,old區(qū)每日幾乎不會有什么變化
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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