JVM學(xué)習(xí)筆記

JVM基本結(jié)構(gòu)

jvm基本結(jié)構(gòu)

PC寄存器

  • 每個線程擁有一個PC寄存器
  • 線程創(chuàng)建時創(chuàng)建PC寄存器
  • 指向下一條指令的地址
  • 執(zhí)行本地方法時,PC的值是undefind

方法區(qū):用來保存類的一些元信息的(例外,jdk1.7String的常量信息已經(jīng)移到了堆)

  • 類的常量池
  • 字段方法信息
  • 方法字節(jié)碼
  • 通常和永久區(qū)(Perm關(guān)聯(lián)在一起)
    永久區(qū)并不是永久不變的,只是相對的穩(wěn)定

  • 和程序開發(fā)密切相關(guān)
  • 應(yīng)用程序?qū)ο蠖急4嬖趈ava堆中
  • 所有線程共享java堆
  • 對于GC來說,堆也是分代的
  • GC的主要工作區(qū)間

桟:

  • 線程私有的
  • 由一系列幀組成(因此java桟也叫做幀桟)
  • 幀保存一個方法的局部變量,操作數(shù)桟,常量池指針
  • 每一次方法調(diào)用創(chuàng)建一個幀,并壓桟


    圖片.png

    reference this 類比于python中的成員方法必須傳self

操作數(shù)桟

圖片.png

桟上分配

  • 小對象(一般幾十個bytes),在沒有逃逸(這個對象在其他線程也會使用到)的情況下,可以直接分配在桟上。
  • 直接分配在桟上,可以自動回收,減輕GC壓力
  • 大對象或者逃逸對象無法再桟上分配。

堆、桟、方法區(qū)的交互

圖片.png

java的內(nèi)存模型

  • 每個線程中有一個工作內(nèi)存和主存獨(dú)立
  • 工作內(nèi)存存放主存中變量的拷貝
  • 可見性:一個線程修改了某個變量,其他線程立即知道
  • 有序性:
    在本線程內(nèi),操作都是有序的
    在線程外觀察,操作都是無序的(指令重排或主內(nèi)存同步延時)

編譯和解釋運(yùn)行的概念

  • 解釋執(zhí)行:
    解釋執(zhí)行以解釋方式運(yùn)行字節(jié)碼
    解釋執(zhí)行的意思是:讀一句執(zhí)行一句
  • 編譯運(yùn)行(JIT):
    將字節(jié)碼編譯成機(jī)器碼
    直接執(zhí)行機(jī)器碼
    運(yùn)行時編譯
    編譯后性能有數(shù)量級的提升

GC的概念

  • 全稱Garbage Collection
  • 1960 List語言首先使用GC
  • java中,GC管理堆空間和永久區(qū)

GC算法

引用計數(shù)法

  • 概念:通過引用計算來回收垃圾
  • 使用者:COM,Python,ActionScript3
  • 原理:
    引用計數(shù)器的實(shí)現(xiàn)很簡單,對于一個對象A,只要有任何一個對象引用了A,則A的引用計數(shù)器就 加1,當(dāng)引用失效時,引用計數(shù)器就減1。只要對象A的引用計數(shù)器的值為0,則對象A就不可能再被使用。
  • 缺點(diǎn):引用和去引用伴隨著加法和減法,影響性能
  • 很難處理循環(huán)引用(垃圾對象的循環(huán)引用,也就是那三個點(diǎn)不會被回收)


    圖片.png

標(biāo)記清除法

  • 原理:
    將垃圾回收分為兩個階段:標(biāo)記階段和清除階段。在標(biāo)記階段,首先通過根節(jié)點(diǎn),標(biāo)記所有從根節(jié)點(diǎn)開始的可達(dá)對象,因此,未標(biāo)記對象就是未引用的垃圾對象,然后在清除階段清除所有未標(biāo)記的對象。
  • 適用場合:適用于存活對象較多的場合,如老年代。

標(biāo)記壓縮法

  • 原理
    它在標(biāo)記清除法的基礎(chǔ)上做了一些優(yōu)化。和標(biāo)記清除法一樣,標(biāo)記壓縮算法也是從根節(jié)點(diǎn)開始,對所有的對象做一次標(biāo)記。但之后,它并不是簡單的清理未標(biāo)記的對象,而是將所有的的存活對象壓縮到內(nèi)存的一端,之后清理邊界外的所有空間。
  • 優(yōu)勢:
    能夠整理內(nèi)存碎片,避免分配大對象時,空間不夠?qū)е翭ullGC
  • 適用場合:適用于存活對象較多的場合,如老年代。

復(fù)制算法

  • 原理:
    將原有的內(nèi)存分為兩塊,每次只是用其中一塊,在垃圾回收時,將正在使用的內(nèi)存中的存活對象復(fù)制到未使用的內(nèi)存塊中,之后清除正在使用的內(nèi)存塊中的所有對象,交換兩個內(nèi)存的角色,完成垃圾回收
  • 優(yōu)點(diǎn):與標(biāo)記清除法相比,復(fù)制算法是一種相對高效的回收算法
  • 缺點(diǎn):浪費(fèi)空間
  • 適用場合:適用于存活對象較少的場合,如新生代。

復(fù)制算法的優(yōu)化:整合標(biāo)記清除思想

image.png
  • 當(dāng)垃圾回收進(jìn)行的時候,大對象進(jìn)入擔(dān)??臻g(因為復(fù)制空間一般不大)
  • 老年對象(好幾次回收都沒有被回收掉的對象)進(jìn)入老年代

可觸及性

可觸及的:

從根節(jié)點(diǎn)可以觸及到這個對象

可復(fù)活的:

一旦所有引用被釋放,就是可復(fù)活狀態(tài),因為在finalize()中可能復(fù)活該對象

不可觸及

  • 在finalize()之后,可能會進(jìn)入不可觸及狀態(tài)
  • 不可觸及狀態(tài)不可能復(fù)活
  • 可以回收


    圖片.png

    第一次GC調(diào)用finalize()方法,因為覆寫了finalize()方法,復(fù)活了obj,所以第一次obj不是nul,finalize()方法只會被調(diào)用一次,第二次gc,obj就不能復(fù)活了

  • 桟中引用的對象
  • 方法區(qū)中靜態(tài)成員或者常量的引用 (全局對象)
  • JNI方法桟中的引用對象

Stop-The-World

  • java中的一種全局停頓現(xiàn)象
  • 全局停頓,所有java代碼停止,native可以執(zhí)行,但不能和jvm交互
  • 多半由于GC引起
  • 原因:類比一邊開party,一邊打掃衛(wèi)生,只能讓大家停下來才能掃干凈。
  • 危害:長時間服務(wù)停止,沒有響應(yīng);遇到HA系統(tǒng),可能引起主備切換,嚴(yán)重危害環(huán)境。

GC參數(shù)

串行收集器(GC線程只有一個)

最古老,最穩(wěn)定,效率高,可能產(chǎn)生較長的停頓
-XX:+UseSerialGC:
新生代、老年代使用串行回收
新生代使用復(fù)制算法,老年代使用標(biāo)記壓縮算法

并行收集器(GC線程有多個)

ParNew:

  • -XX:+UseParNewGC:
  • 新生代并行,老年代還是串行
  • -XX:+ParallelGCThreads 限制線程數(shù)量

Parallel收集器

  • 類似ParNew
  • 新生代復(fù)制算法
  • 老年代標(biāo)記壓縮算法
  • 更加關(guān)注吞吐量
  • -XX:+UseParallelGC
    老年代串行
  • -XX:+UseParallelOldGC
    老年代并行
  • XX:MaxGCPauseMills
    最大停頓時間
  • XX:GCTimeRatio
    0-100取值范圍
    垃圾收集時間占總時間比
    默認(rèn)99,即最大允許1%時間做GC
  • 兩個參數(shù)是矛盾的。因為停頓時間和吞吐量不可能同時調(diào)優(yōu)

CMS收集器

  • Concurrent Mark Sweep 并發(fā)標(biāo)記清除(并發(fā)指的是和應(yīng)用程序一起執(zhí)行)
  • 標(biāo)記清除算法
  • 并發(fā)階段會降低吞吐量
  • 老年代收集器(新生代使用ParNew)
  • XX:+UseConcMarkSweepGC
  • 停頓時間較少
    -過程比較復(fù)雜
    初始標(biāo)記:根可以直接關(guān)聯(lián)到的對象,速度快
    并發(fā)標(biāo)記(和用戶線程一起):主要標(biāo)記過程,標(biāo)記全部對象
    重新標(biāo)記:由于并發(fā)標(biāo)記時,用戶線程依然運(yùn)行,因此在正式清理前,再做修正
    并發(fā)清理(和用戶線程一起)基于標(biāo)記結(jié)果,直接清理對象


    CMS收集器
  • 清理不徹底
  • 不能在空間快滿的時候清理
  • -XX:CMSInitiatingOccupancyFraction設(shè)置觸發(fā)GC的閾值
  • 如果不幸預(yù)留的空間不夠,就會引起concurrent mode failure


    CMS不用標(biāo)記壓縮但是也會使用命令進(jìn)行整理碎片

GC參數(shù)整理

GC參數(shù)整理

GC參數(shù)整理

類裝載器

class轉(zhuǎn)載驗證流程

加載

  • 裝載類的第一個階段
  • 取得類的二進(jìn)制流
  • 轉(zhuǎn)為方法區(qū)數(shù)據(jù)結(jié)構(gòu)
  • 在java堆中生成對應(yīng)的java.Lang.Class對象

鏈接

  • 驗證:
    目的是保證Class流的格式正確
    文件格式驗證:
    ? ? ? ?是否以0xCAFEBABE開頭
    ? ? ? ?版本號是否合理
    元數(shù)據(jù)驗證:
    ? ? ? ?是否有父類
    ? ? ? ?是否繼承了final類
    ? ? ? ?非抽象類實(shí)現(xiàn)了所有抽象方法
    字節(jié)碼驗證(很復(fù)雜):
    ? ? ? ?運(yùn)行檢查
    ? ? ? ?桟數(shù)據(jù)類型和操作碼數(shù)據(jù)是否吻合
    ? ? ? ?跳轉(zhuǎn)指令指定到合理的位置
    符號引用驗證:
    ? ? ? ?常量池描述類是否存在
    ? ? ? ?訪問的方法或字段是否存在且足夠的權(quán)限
  • 準(zhǔn)備:
    分配內(nèi)存,并為類設(shè)置初始值(方法區(qū)中)
    ? ? ? ?public static int v=1
    ? ? ? ?在準(zhǔn)備階段,v會被設(shè)置為0
    ? ? ? ?在初始化的<clinit>中才會被設(shè)置為1
    ? ? ? ?對于static final類型,在準(zhǔn)備階段就會被賦值
  • 解析:
    將符號引用替換為直接引用
    符號引用:字符串,引用對象不一定被加載。就是一個類的字符串表現(xiàn),比如說"java.lang.Object"
    直接引用:指針或地址偏移量,引用對象一定在內(nèi)存

初始化

  • 執(zhí)行類的構(gòu)造器<clinit>
    static 變量 賦值語句
    static{}語句
  • 子類的<clinit>調(diào)用前保證父類的<clinit>被調(diào)用
  • <clinit>是線程安全的

什么是ClassLoader

  • ClassLoader是一個抽象類
  • ClassLoader的實(shí)例將讀入java字節(jié)碼然后將類裝載到j(luò)vm中
  • CLassLoader可以定制,滿足不懂的字節(jié)碼流獲取方式
  • ClassLoader負(fù)責(zé)類裝載過程的加載階段
  • 一些重要方法


    ClassLoader的重要方法
  • ClassLoader的加載順序


    圖片.png

自底向上查詢類是否已經(jīng)加載,自頂向下嘗試加載類。
我們寫的類一般都是在App ClassLoader中加載的,如果要查詢一個類是否已經(jīng)被加載,先從App ClassLoader中查找,找不到再網(wǎng)上找。如果要加載一個類,先問一下Bootstrap ClassLoader有沒有,沒有再往下走。
雙親模式的問題
BootstrapLoader不能查找App ClassLoader中的類
解決方法:Thread.setContextClassLoader()
這是一個上下文加載器,是一個角色,用以頂層ClassLoader無法訪問底層ClassLoader的問題,基本思想是在頂層ClassLoader中傳入底層ClassLoader實(shí)例

java堆的分析

內(nèi)存溢出(OOM)的原因

  • 在jvm中的內(nèi)存區(qū)間劃分
    堆,永久區(qū),線程桟,直接內(nèi)存
  • 占用大量堆空間,直接溢出
  • 永久區(qū)溢出,可以增大Perm區(qū)大小,運(yùn)行Class回收
  • java棧溢出
    這里的桟指的是在創(chuàng)建線程的時候,需要為線程分配桟空間,這個桟空間是向操作系統(tǒng)請求的,如果操作系統(tǒng)無法給出足夠的空間,就會拋出OOM。解決辦法:減少堆內(nèi)存,減少線程桟的大小

Mark Word,對象頭標(biāo)記 32位

描述對象的hash,鎖信息,垃圾回收的年齡,標(biāo)記
指向鎖記錄的指針
指向monitor的指針
GC標(biāo)志
偏向鎖線程ID

偏向鎖

  • 大部分情況下是沒有競爭的,所以可以通過偏向來提高性能
  • 所謂的偏向,就是偏心,即鎖會偏向于當(dāng)前已經(jīng)占有的鎖的線程
  • 將對象投Mark的標(biāo)記設(shè)置為偏向,并將線程ID寫入對象頭
  • 只要沒有競爭,獲得偏向鎖的線程,在將來進(jìn)入同步快,不需要做同步
  • 當(dāng)其他線程請求相同的鎖時,偏向模式結(jié)束
  • 在競爭激烈的場合,偏向鎖會增加系統(tǒng)負(fù)擔(dān)

輕量級鎖 BasicObjectLock

  • 嵌入在線程桟中對象
    包含兩個部分,一個是對象頭,另一個是指向持有這個鎖的對象的指針
  • 普通的鎖處理性能不夠理想,輕量級鎖是一種快速鎖定的方法。
  • 如果對象沒有被鎖定
    將對象頭的Mark指針保存到鎖對象中
    將對象頭設(shè)置為指向鎖的指針(在線程桟空間中)
  • 如果輕量級鎖失敗,表示存在競爭,升級為重量級鎖(常規(guī)鎖)
  • 在沒有競爭的前提下,減少傳統(tǒng)鎖使用OS互斥量產(chǎn)生的性能損耗
  • 在競爭激烈時,輕量級鎖會多做很多額外的操作,導(dǎo)致性能下降。

自旋鎖

  • 當(dāng)競爭存在時,如果線程可以很快獲得鎖,那么可以不在OS層掛起線程,讓線程做幾個空操作(自旋)
  • 如果同步塊很長,自旋失敗,會降低系統(tǒng)性能
  • 如果同步塊很短,自旋成功,節(jié)省線程掛起切換的時間,提升系統(tǒng)性能。

獲取鎖的流程

首先嘗試獲取偏向鎖,如果可用,進(jìn)入偏向模式,如果不可用,嘗試輕量級鎖,如果可用,使用輕量級鎖,到此結(jié)束,如果不可用,嘗試自旋鎖,如果成功,那就拿到鎖了,如果不成功,最后才會膨脹為重量鎖(普通鎖),在操作系統(tǒng)層面進(jìn)行掛起。

代碼層面鎖的優(yōu)化

減少鎖的持有時間

盡量使用同步代碼塊而不是同步方法

減小鎖的粒度

  • 將大對象,拆成小對象,大大增加并行度,降低鎖競爭
  • 偏向鎖,輕量級鎖成功率更高
  • ConcurrentHashMap
    若干個Segment,一個Segment維護(hù)一個HashEntry,put操作時,先定為到Segment,鎖定一個Segment,執(zhí)行put
    減小鎖粒度后,ConcurrentHashMap允許多個線程同時進(jìn)入

鎖分離

  • 根據(jù)功能分離
  • ReadWriteLock
  • 讀多寫少的情況,可以提高性能

鎖粗化

圖片.png

鎖消除

在即時編譯時,如果發(fā)現(xiàn)不可能被共享的對象,則可以消除這些所的操作

無鎖

  • CAS(Compare And Swap)
  • 非阻塞同步
  • CAS(V,E,N)
  • 應(yīng)用層面判斷多線程的干擾,如果有干擾,則通知線程重試。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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