主頁 > 後端開發 > JVM全方位解讀(附面試題)

JVM全方位解讀(附面試題)

2020-11-19 23:30:52 後端開發

java中就虛擬機是其他語言撰寫的(C語言+匯編語言,因此,JVM最常出現的攻擊就是buffer overflow),如javac命令等,而java apijava寫的,大多開源在openjdkjdk中有一個src.jar,就是JDk的原始碼,本文是JVM基礎知識的一個匯總,方便查閱,內容較多,以下是內容目錄,可以直接跳到感興趣的章節,

  1、JVM的記憶體模型

  2、堆疊的例外總結

  3、常用的JVM引數設定

  4、JVM的分代介紹

  5、GC的回收程序

  6、GC的回收演算法

  7、GC的回收器

  8、JVM的優化

  9、JVM的記憶體分析工具

  10、物件的創建

  11、物件的記憶體布局

  12、物件的監視器

  13、類加載器、反射、雙親委派

1、JVM的記憶體模型

JDK7記憶體模型(圖來自于網路):                       

JDK8記憶體模型(圖來自于網路):

JVM記憶體模型說明:

 JDK7中記憶體模型包括:方法區,堆區,堆疊區,本地方法堆疊,計數器,分為兩個型別的區域,一種是執行緒私有的,一種是執行緒共享的,其中,方法區和堆區是執行緒共享的,其他都是執行緒私有的,堆區是被GC的主要區域,方法區有部分廢棄的常量可以被GC,下面詳細的說明:

 (1)計數器,執行緒私有,當前執行緒所執行的位元組碼的行號指示器,標記當前執行到了哪一行指令

 (2)虛擬機堆疊,執行緒私有,生命周期和執行緒相同,存區域變數表、運算元堆疊、動態鏈接、方法出口(即方法回傳地址)資訊等

  其中,區域變數表存編譯器可知的各種資料型別,包括boolean、char、byte、short、int、float、double,及物件的參考

 (3)本地方法堆疊,和虛擬機堆疊所發揮的作用非常相似,區別是,虛擬機堆疊為虛擬機執行java方法(也就是位元組碼)服務,而本地方法堆疊則為虛擬機使用到的native方法服務, HotSpot 虛擬機中和 Java 虛擬機堆疊合二為一,本地方法被執行的時候,在本地方法堆疊也會創建一個堆疊幀,用于存放該本地方法的 區域變數表、運算元堆疊、動態鏈接、出口資訊

 (4)堆,java虛擬機所管理的記憶體中最大的一塊,在虛擬機啟動時創建,此記憶體區域的唯一目的就是存放物件實體,幾乎所有的對 象實體以及陣列都在這里分配記憶體

 (5)方法區,用于存盤已被虛擬機加載的類的資訊(欄位、方法)、常量、靜態變數、即時編輯器編譯后的代碼等資料,運行時常量池:屬于方法區,包含字面量(字串、final常量)、符號參考

  其中,方法區的詳細說明:

  方法區,Java虛擬機規范把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫 Non-Heap(非堆),目的應該是與 Java 堆區分開來,方法區也被稱為永久代,但方法區和永久代并不完全等同,只是為了便于管理,這樣,HotSpot虛擬機的垃圾收集器就可以像管理java堆一樣管理這部分記憶體,方法區可被GC回收的那部分記憶體是廢棄的常量,但這樣管理并不好,會容易產生記憶體溢位,所以在JDK8中,移除了方法區,將方法區中的常量池移至堆中(字串池和類的靜態變數放入java堆中),其他移到直接記憶體中,命名為“元空間”,解決了永久代會出現記憶體溢位的問題,但是要注意,需要設定元空間的最大大小(-XX:MaxMetaspaceSize設定)否則,如果不指定大小的話,隨著更多類的創建,虛擬機會耗盡所有可用的系統記憶體

 (6)直接記憶體,不屬于JVM運行時資料區,JVMNIO方法可以分配堆外記憶體如使用 DirectByteBuffer

2、堆疊的例外總結

   (1)堆記憶體溢位OutOfMemoryError:java heap space

   產生原因:java堆用于存盤物件實體,只要不斷的創建物件,并保證GC roots到物件間有可達路徑避免這些物件的GC,那么,當物件數量到達堆的最大容量限制后就會產生OOM

   解決辦法:

  1. 通過引數 -XX:HeapDumpOnOutOfMemoryError 可以讓虛擬機在記憶體溢位例外時Dump當前記憶體堆轉儲快照
  2. 通過記憶體映像分析工具(如:Eclipse Memory Analyzer)對Dump出的堆轉儲快照分析,判斷是記憶體泄露還是記憶體溢位
  3. 如果是記憶體泄露:通過工具查看泄露物件的型別資訊和它們到 GC Roots 的參考鏈資訊,分析GC收集器無法自動回收它們的原因,定位記憶體泄露的代碼位置
  4. 如果是記憶體溢位:檢查堆引數 -Xms-Xmx,看是否可調大;代碼上檢查某些物件生命周期過長,持有時間過長的情況,嘗試減少程式運行期間記憶體消耗

  (2)除程式計數器外,JVM其他幾個運行時區域都可能發生OutOfMemoryError例外

  (3)堆疊的兩種例外

    1. StackOverFlowError例外:執行緒請求的堆疊深度大于虛擬機所允許的最大深度

    一個會發生stackOverFlow的場景:無限遞回,沒有出口

    2.OutOfMemoryError例外:虛擬機擴展堆疊時無法申請足夠的記憶體空間

    一個會發生OutOfMemory的場景:list,無限添加元素

    解決虛擬機堆疊兩種例外的辦法:  

      1.檢查代碼中是否有死遞回

      2.配置 -Xss 增大每個執行緒的堆疊記憶體容量,但會減少作業執行緒數,需要權衡

3、常用的JVM引數設定

  (1)-Xss,設定堆疊的大小不熟悉最好使用默認值,當堆疊中存盤資料比較多時,需要適當調大這個值,否則會出現java.lang.StackOverflowError例外

  (2) 堆記憶體設定:

    ① -Xms初始堆大小,ServerJVM最好將-Xms-Xmx設為相同值開發測驗機JVM可以保留默認值

    ② -Xmx,最大堆大小,默認為物理記憶體的1/4,最佳設值應該視物理記憶體大小及計算機內其他記憶體開銷而定

     默認空余堆記憶體小于 40%時,JVM就會增大堆直到-Xmx的最大限制;空余堆記憶體大于70%時,JVM會減少堆直到-Xms的最小限制,因此服務器一般設定-Xms-Xmx相等以避    免在每次GC 后調整堆的大小,

    ③ -Xmn:年輕代大小(young區大小),通常為 Xmx 1/3 1/4,不熟悉最好保留默認值

    ④ -XX:SurvivorRatio,設定年輕代Eden和單個S區的比例,默認為8,即Eden8/10,兩個survivor分別為1/10,其中一個survivor閑置,用于復制,所以年輕代實際可用的記憶體    大小為-Xmn設定值的9/10Eden設定的太大的話,會導致GC變慢,并且沒有足夠的survivor幸存者空間,會導致GC直接到達老年代,老年代滿的更快,會更早觸發FullfGC,     Eden設定的過小,則MinorGC頻繁,會影響線上程式運行,因為GC會導致應用程式暫停

    ⑤ -XX:MaxTenuringThreshold設定年輕代中回收區物件的年齡,默認15,可通過命令指定,如果設定為0,表示Eden回收時,不經過Survivor,直接到達老年代

   (3) 持久代設定:

    ① -XX:PermSize,方法區(永久代,或稱非堆區)初始分配的記憶體大小,其全稱permanent size(持久化記憶體)

    ② -XX:MaxPermSize=64m,永久代的最大大小,超過這個值,將會拋出OutOfMemoryError:PermGen

     在配置之前一定要慎重的考慮一下自身軟體所需要的非堆區記憶體大小,因為持久代記憶體是不會被java垃圾回識訓制進行處理的地方,

     最大堆記憶體與最大非堆記憶體的和絕對不能夠超出作業系統的可用記憶體,

    (4) 元空間的設定:

    JDK 1.8 的時候,方法區(HotSpot 的永久代)被徹底移除了(JDK1.7 就已經開始 ),取而代之是元空間,元空間使用的是直接記憶體,配置如下:

    -XX:MetaspaceSize=N //設定 Metaspace 的初始(和最小大小)

    -XX:MaxMetaspaceSize=N //設定 Metaspace 的最大大小

    與永久代很大的不同就是,元空間解決了永久代易發生記憶體溢位的問題,但是要注意,如果不指定元空間的大小,隨著更多類的創建,虛擬機會耗盡所有可用的系統記憶體,

 4、JVM的分代介紹

  因為GC垃圾回收的主要區域是堆區,從GC的角度來說,java堆又細分為新生代和老年代,另外有一部分是持久代,來代表方法區,是為了方便管理,是方法區在堆區開辟出來的一塊邏輯空間,下圖為分代示意圖(圖片來自于網路)

  

  (1)新生代,新生代又分為三個區域,包括Eden區和兩個Survivor幸存者區,Eden區主要存放new出來的物件,Survivor主要存放上一次GC之后的幸存者,作為這一次GC的被掃描者,分別對應圖中的S0和S1,兩個Survivor區等大,其中一個閑置,所以新生代實際可用的記憶體大小要減去其中一個幸存者區域的大小

  (2)老年代,老年代主要存放大物件,或從MinorGC過來的到達一定年齡仍然幸存的物件

  (3)持久代,即方法區,用于存放已被虛擬機加載的類的資訊,靜態變數,常量,和編譯后的代碼等資訊,持久代能被GC的是廢棄的常量,持久代對垃圾回收沒有顯著影響

 5、GC的回收程序

  (1) MinorGC的程序:MinorGC采用復制演算法,首先,把Eden區域和使用的幸存者區域(SurvivorFrom)中存活的物件復制到另一塊空閑的幸存者區(SurvivorTo)中,同時,把這些物件的年齡+1如果有物件的年齡已經達到了老年代的標準,則賦值到老年代區,如果空閑的幸存者區(SurvivorTo)不夠存放Eden和使用的幸存者區(SurvivorFrom)移動過來的資料,則直接放到老年代,然后,清空Eden和使用的幸存者區中(SurvivorFrom)的物件,然后,幸存者區互換,SurvivorTo中的資料換到SurvivorFrom中,SurvivorFrom繼續等待下一次GCSurvivor區每熬過一次MinorGC,就將物件的年齡+1,當物件的年齡到達某個值時(默認時15,可用通過引數-XX:MaxTenuringThreshold 來設定),這些物件就會成為老年代

   (2) MajorGC的程序:老年代的回收,不會那么頻繁,老年代使用的回收演算法是標記-清除或標記-整理演算法,標記-清除演算法會產生記憶體碎片,即不連續的空間,如果此時,有大的物件進來,記憶體中沒有足夠的連續空間時,會提前觸發FullGC(這是一個優化點),一次FullGC的時間要比一次MinorGC的時間長,當年老代也裝不下,就會拋出OOM(Out Of Memory)例外  (3) Full GC的觸發:老年代滿了而無法容納更多的物件,會觸發Full GCFull GC 清理整個記憶體堆包括年輕代和老年代,

 6、GC的回收演算法

  (1) 標記-清除演算法,缺點是容易產生碎片,且效率不高,標記程序和清除程序效率都不高

    標記-清除演算法的程序,分為兩個程序,標記程序和清除程序

    ① 標記程序,遍歷所有的GC-roots,然后將所有GC-roots可達的物件標記為存活的物件(記為1)

    ② 清除程序,遍歷堆中的所有物件,將沒有標記的物件全部清除掉(沒有標記過的,記為0)

    ③ 清除過后,被標記過的物件留下,標志位重新歸0

  以下是標記-清除演算法的圖示(圖片來自于網路)

   

  (2) 復制演算法,用于年輕代,需要額外的空間來進行復制操作

    復制演算法的程序,就是把記憶體分為2塊等同大小的記憶體空間(AB),使用A進行記憶體的使用,當A記憶體不足以分配物件而引起記憶體回收時,就會把存活的物件從A記憶體塊放到B內    存塊,然后把A記憶體塊中的物件全部清除掉,然后在B記憶體塊中使用,當B記憶體不足以分配物件而引起記憶體回收時,就會把存活的物件從B記憶體塊放到A記憶體塊中,然后把B記憶體    塊中的物件全部清除掉,如此回圈

    復制演算法的好處是,避免的空間碎片(記憶體中不連續的空間),缺點是浪費了一半的空間,降低空間使用率

   以下是復制演算法的圖示(圖片來自于網路)

     

  (3) 標記-整理演算法(Mark-Compact),用于老年代

    標記-整理演算法的程序,標記程序仍然與標記-清除演算法一樣,但后續步驟不是直接 對可回收物件進行清理,而是讓所有存活的物件都向一端移動,然后直接清理掉端邊界以     外的記憶體

  以下是標記-整理演算法的圖示(圖片來自于網路)

   

   從圖中可以看出,標記-整理演算法,避免了標記-清除演算法產生記憶體碎片的問題, 也避免了復制演算法中記憶體浪費的問題,存在的問題就是效率問題,比前兩者效率低 

  (4) 分代收集,JVM實際GC中使用的,根據物件存活周期的不同,分新生代和老年代,新生代使用復制演算法,因為每次GC只有少量的物件存活,用復制演算法只需要付出少量存活對    象的復制成本就可以完成收集,老年代使用標記-整理演算法或標記-清除演算法,老年代中,物件存活率高,沒有額外的擔保空間,就必須使用標記-清除或標記-整理演算法

   以下是分代收集演算法的圖示(圖片來自于網路)

  

7、GC的回收器

1、收集器

(1) serial收集器,是單執行緒收集器,用于新生代,使用復制演算法,在GC時,會暫停其他所有作業的執行緒,直到GC結束,常用于client模式下的虛擬機

(2) parNew收集器,是serial的多執行緒版本,也用于新生代,使用復制演算法

parNew和serial都可以且只能和老年代的CMS和serial old組合使用

(3) Parallel Scarenge收集器,用于新生代,使用復制演算法,主要關注吞吐量,適合在后臺運算且不需要太多互動的任務

(4) Serial old收集器,是serial收集器的老年代版本,是單執行緒收集器,使用標記-整理演算法,也是給client下的虛擬機用

(5) Parallel old,用于老年代,使用標記-整理演算法,注重吞吐量,及CPU敏感的場合優先考慮使用parallel + parallel old組合

(6) CMS(concurrent Mark Sweep)收集器,特點是獲取最短回收停頓時間為目標的收集器,使用標記-清除演算法,支持并發、低停頓,缺點是對CPU資源敏感(在并發階段,雖然不會導致用戶執行緒停頓,但會因為占用一部分執行緒(或CPU資源),而導致應用程式變慢),會導致吞吐量降低,且無法收集浮動垃圾(標記-清除演算法,會產生大量的碎片),會導致FullGC,可以用serial old臨時替代

(7) G1,JDK7中新增的回收器,是JDK9默認的回收器,特點是,面向服務端應用的垃圾收集器,支持并發與并行,充分利用CPU多核,縮短stop時間,且可預測停頓,采用分代收集,整體是標記-整理演算法,區域是復制演算法,不會產生碎片

2、G1回收器的作業原理

  (1)G1的分代收集,如下圖(圖片來自于網路)

  

   G1的分代收集,將整個堆分成n個大小相等的Region區域,每個Region占用一塊連續的虛擬記憶體地址,新生代和老生代不再是物理隔離,而是一部分Region的集合,Region的大小可以通過-XX:G1HeapRegionSize設定,如果未設定,默認是2048份,G1仍是分代收集,除Eden、Survivor、Old區域外,還包含Humongous,是專門用來存放巨型物件的,即占用空間>50%磁區容量的物件,以此減少短期存在的巨型物件對垃圾收集造成的負面影響,

  G1的回收程序:  

  (1)標記程序,G1的標記分為幾個階段,包括全域并發標記,初始標記,并發標記,最終標記

    i. 全域并發標記:基于 STAB(snapshot-at-the-beginning)形式的并發標記,標記完成后,G1知道哪個區域是空的,它首先會收集那些產生大量空閑空間的區域

    ii.初始標記STW:耗時很短,標記GC-roots能直接關聯的物件,壓入掃描堆疊

    iii.并發標記:與用戶執行緒并發執行,耗時較長,GC執行緒不斷從掃描堆疊中取出參考,然后遞回標記,直到掃描堆疊清空

    iv.最終標記STW:重新標記并發標記期間因用戶程式執行而導致參考發生變動的那部分標記,寫入屏障Write Barrier標記的物件

  (2)清理程序:統計各個Region被標記存活的物件有多少,如果發現沒有存活,就會整體回收到可分配的Region中

  (3)拷貝存活物件:將Region中的存活物件拷貝到空Region里去,回收原Region空間,繼續使用

 

  G1不存在Full GC,分為Yong GC和Mix GC兩種,Yong GC是新生代的回收,Mix GC是老年代的收集

8、JVM的優化

  這是很重要的一部分,JVM引數平時不需要頻繁的去調整,可以通過觀察應用環境的運行,定期的調整,主要有兩方面,一個是JVM引數的調整,一個是GC的優化,GC優化的目標是盡量減少GC次數,盡量在Yong區完成GC,盡量減少Full GC的次數,以減少GC對應用程式帶來的影響

  (1)JVM引數(這里列出所有可調整的JVM引數,可根據各自的環境酌情設定)

    -Xms4G 是指: JVM啟動時整個堆(包括年輕代,年老代)的初始化大小

    -Xmx4G 是指: JVM啟動時整個堆的最大值,默認為物理記憶體的1/4

      -Xms和-Xmx的設定,默認空余堆記憶體小于 40%時,JVM就會增大堆直到-Xmx的最大限制;空余堆記憶體大于70%時,JVM會減少堆直到-Xms的最小限制,因此服務器一般       設定-Xms-Xmx相等以避免在每次GC 后調整堆的大小

 

    -Xmn2G是指:年輕代的空間大小,通常為 Xmx 1/3 1/4剩下的是年老代的空間

    -XX:SurvivorRatio=1是指:年輕代Eden區和單個S區的比例,默認是8,即Eden占8/10,兩個Survivor分別占1/10,其中一個Survivor閑置,用于復制,所以年輕代實際可用的記憶體大小未-Xmn設定值的9/10,Eden設定的太答的話,會導致GC變慢,并且沒有足夠的幸存者空間,會導致GC直接到達老年代,老年代滿的更快,會更早觸發Full GC,Eden設定的過小,則Minor頻繁,會影響線上程式運行,因為GC會導致應用程式暫停

    -XX:MaxTenuringThreshold設定年輕代中回收區物件的年齡,默認15,可通過命令指定,如果設定為0,表示Eden回收時,不經過Survivor,直接到達老年代

    -XX:NewRatio設定新生代與老年代的比例,如 –XX:NewRatio=2,則新生代占整個堆空間的1/3,老年代占2/3

  (2)GC的引數優化

    i.CMS回收器的引數優化

    -XX:CMSInitiatingOccupancyFraction=70,該值代表老年代堆空間的使用率,默認值是92,假如設定為70,就表示第一次 CMS 垃圾收集會在老年代占用 70% 時觸發,過大會使 STW 時間過,過小會影響吞吐率

    -XX:+UseCMSCompactAtFullCollection-XX:CMSFullGCsBeforeCompaction=4執行4次不壓縮的 Full GC 后,會執行一次記憶體壓縮的程序,用來消除記憶體碎片

    -XX:+ConcGCThreads并發 CMS 程序運行時的執行緒數,CMS 默認回收執行緒數是(CPU+3) / 4,更多的執行緒會加快并發垃圾回收程序,但會帶來額外的同步開銷,

    ii.G1回收器的引數優化

    -XX:G1HeapRegionSize,指定G1Rigion的大小,如果未設定,默認將堆記憶體平均分為 2048

    -XX:MaxGCPauseMillis=n,設定GC時最大暫停時間,這個目標不一定能滿足,JVM會盡最大努力實作它,不建議設定的過小(<50ms

    -XX:InitiatingHeapOccupancyPercent=n,觸發G1啟動 Mixed GC,表示垃圾物件在整個G1 堆記憶體空間的占比  

    避免使用 -Xmn -XX:NewRatio等其他顯式設定年輕代大小的選項,固定年 輕代大小會覆寫暫停時間目標

9、JVM的記憶體分析工具   

  JVM自帶的記憶體分析小工具:jconsolejhatjmapjstackjstatjstatdjvisualvm

  linux工具:pidstatvmstatiostat

  eclipse的分析工具:mat

  收費工具:Jporfileryourkit

   

  (1) jconsole,jvm自帶記憶體分析工具,位于jdkbin目錄下,它提供了圖形界面,可以查看到被監控的jvm的記憶體資訊,執行緒資訊,類加載資訊,MBean資訊,

  (2) jhat,jvm自帶記憶體分析工具,位于jdkbin目錄下jdk6+版本自帶,能夠分析dump檔案,執行 jhat -J -Xmx512m [file] file就是dump檔案路徑,

  (3) jmap,jvm自帶記憶體分析工具,位于jdkbin目錄下,傾向于分析jvm記憶體中物件資訊,jmap -histo <pid>在螢屏上顯示出指定pidjvm記憶體狀況,太簡單,

  jmap -dump:file=c:\dump.txt 340  匯出dump檔案,用專門的dump分析工具分析,

  (4) jstack,jvm自帶記憶體分析工具,位于jdkbin目錄下,會顯示執行緒優先級,執行緒IDnative執行緒ID,執行緒堆疊起始地址

  (5) jstat,jvm自帶記憶體分析工具位于jdkbin目錄下,傾向于分析jvm記憶體的gc情況,常用引數-gcutil,這個引數的作用不斷的顯示當前指定的jvm記憶體的垃圾收集的資訊, jstat -gcutil 340 10000,這個命令是每個10秒鐘輸出一次jvmgc資訊,10000指的是間隔時間為10000毫秒,

  (6) jstatd,jvm自帶記憶體分析工具,位于jdkbin目錄下,一個RMIserver,它可以監控HotspotJVM的啟動和結束,同時提供介面可以讓遠程機器連接到JVM, 比如 jps  jstat都可以通過jstatd來遠程觀察JVM的運行情況,

  (7) jvisualvm,jvm自帶記憶體分析工具,位于jdkbin目錄下JDK6 update 7之后推出,,java可視化虛擬機,它不但提供了jconsole類似的功能,還提供了jvm記憶體和cpu實時診斷,還有手動dumpjvm記憶體情況,手動執行gc,和jconsole一樣,運行jviusalvm,在jdkbin目錄下執行jvisualvmwindows下是jvisualvm.exe,linuxunix下是jvisualvm.sh

  (8) pidstatlinux系統下使用,需要安裝,yum install sysstat,要查看Linux下面行程、行程組、執行緒的資源消耗的統計資訊,可以使用pidstat,它可以收集并報告行程的統計資訊,

  (9) vmstatlinux系統下使用,需要安裝,vmstatVirtual Meomory Statistics(虛擬記憶體統計)的縮寫,可對作業系統的虛擬記憶體、行程、CPU活動進行監控,是對系統的整體情況進行統計,不足之處是無法對某個行程進行深入分析,

  (10) iostatlinux系統下使用,需要安裝,yum install sysstatiostatI/O statistics(輸入/輸出統計)的縮寫,iostat工具將對系統的磁盤操作活動進行監視,它的特點是匯報磁盤活動統計情況,同時也會匯報出CPU使用情況,iostat也有一個弱點,就是它不能對某個行程進行深入分析,僅對系統的整體情況進行分析

  (11) MAT,可以通過MAT分析記憶體泄漏的原因

10、物件的創建

  在語言層面,創建物件有四種方式:1) clone 2)反序列化 3)反射 4) New

   而在虛擬機中,物件創建的程序是如何呢?

   JAVA編譯解釋的程序:.java檔案->javac編譯成.class位元組碼檔案->jvm解釋執行,

     Java很特殊,Java程式需要編譯但是沒有直接編譯成機器語言,即二進制語言,而是編譯成位元組碼(.class)再用解釋方式執行,java程式編譯以后的class屬于中間代碼,并不是可執行程式exe,不是二進制檔案,所以在執行的時候需要一個中介來解釋中間代碼,這既是java解釋器,也就是所謂的java虛擬機(JVM),也叫JDK,

  

  JVM中,物件創建程序(New)分三步:1) 類加載 2) 為新物件分配記憶體 3)初始化

   在虛擬機遇到new指令時:

  1) 類加載:確保常量池中存放的時已解釋的類,且物件所屬型別已經初始化過,如果沒有,則先執行類加載

  2) 為新生物件分配記憶體:物件所需記憶體大小在類加載時可以確定,將確定大小的記憶體Java堆中劃分出來

  • 分配空閑記憶體方法:
    • 指標碰撞:假如堆是規整的,用過的記憶體和空閑的記憶體各一邊,中間使用指標作為分界點,分配記憶體時將指標移動物件大小的距離
    • 空閑串列:假如堆是不規整的,虛擬機需要維護哪些記憶體塊是可用的串列,分配時候從串列中找出足夠大的空閑記憶體劃分,并更新串列記錄
  • 物件創建在并發情況下保證執行緒安全:例如,正在給物件A分配記憶體,指標還沒修改,物件B同時使用了原來的指標來分配記憶體
    • CAS配上失敗重試
    • 本地執行緒分配緩沖TLAB(ThreadLocal Allocation Buffer):將記憶體分配動作按執行緒劃分到不同空間中進行,即每個執行緒在Java堆中預先分配一塊記憶體

  3) 將分配的記憶體空間初始化為零值:保證物件的實體在Java代碼中可以不賦值就可  直接使用,能訪問到這些欄位的資料型別對應的零值(例如,int型別引數默認為0)

  4) 設定物件頭:設定物件的類的元資料資訊、哈希碼、GC分代年齡等

  5) 執行<init>方法初始化:將物件按照程式員的意愿初始化

 

 11、物件的記憶體布局

  在HotSpot虛擬機中,物件在記憶體中存盤的布局分為3個區域,如下圖:

 

   (1)物件頭(Header):

    i. MarkWord存盤物件自身的運行時資料,例如:哈希碼HashCodeGC分代年齡、鎖狀態標志、執行緒持有的鎖、偏向執行緒ID等,考慮空間效率,Mark設計為非固定的資料結構,它根據物件的不同狀態復用自己的空間,如下表格:

     ii. 指向Class的指標:即物件指向它的類的元資料的指標,虛擬機通過這個指標來確定是哪個類的實體

    iii. 如果物件是Java陣列,物件頭中還需要一塊記錄陣列長度的資料

  (2) 實體資料(Instance Data)物件真正存盤的有效資訊,也是程式代碼中定義的各種型別欄位的內容

  (3) 對齊填充(Padding):起占位符的作用,因為HotSpot VM的要求物件起始地址必須是8位元組的整數倍,也就是物件的大小必須是8位元組的整數倍,當物件實體資料部分沒有對齊時,需要對齊填充來補充

 

  12、物件的監視器

  什么是物件監視器,監視器是一種同步結構,它基于互斥鎖,允許執行緒同時互斥(使用 鎖)和協作

    互斥是,當一個執行緒訪問受保護的資料時,如果沒有其他執行緒在等待, 執行緒獲取鎖 并繼續執行,當執行緒完成執行時,它釋放鎖并退出監視器,但如果此時另一個執行緒已經擁有監視器時,它必須在entry-set中等待,當前面的執行緒 執行完畢退出監視器時,新到達的執行緒必須與在入口集中等待的其他執行緒競爭,只有一 個執行緒能贏得競爭并擁有鎖,

    協作是,當一個執行緒需要資料在某一個狀態下它才能執行,那么另一個執行緒負責將資料 改變到此狀態

 

  物件監視器的一個理解:物件監視器,任意執行緒對Object的訪問,首先要先獲得Object 的監視器,如果獲取失敗了,執行緒進入同步佇列,執行緒狀態變為BLOCKED,當訪問Object 的執行緒(獲得了所的執行緒)釋放了鎖,則該釋放操作喚醒在同步佇列中的執行緒,使其重 新嘗試對監視器的獲取,Thread類提供一個holdsLock(Object obj)方法,當且僅當物件 obj的監視器被某條執行緒持有的時候才回傳true,注意這是一個static方法,意味著某 條執行緒指的是當前執行緒

 

  常見的如生產者/消費者的問題,當讀執行緒需要緩沖區處于“不空”的狀態它才可以從 緩沖區中讀取任何資料,如果它發現緩沖區為空,則進入wait-set等待,待寫執行緒用數 據填充緩沖區,再通知讀執行緒進行讀取,這種機制被稱為Wait and Notify”或“Signal and Continue

 

   下圖描述了物件、物件的監視器、同步佇列和執行執行緒之間的關系,  

  

 

   從上圖中可以看到,任意執行緒對ObjectObjectsynchronized保護)的訪問,首先 要獲得Object的監視器,如果獲取失敗,執行緒進入同步佇列,執行緒狀態變為BLOCKED 當訪問Object的前驅(獲得了鎖的執行緒)釋放了鎖,則該釋放操作喚醒阻塞在同步隊 列中的執行緒,使其重新嘗試對監視器的獲取,

 

  那么,物件的監視器到底是做什么的,用在哪里,起什么作用?

  為什么使用術語監視器而不是鎖定嚴格來說,確實不同,是指具有獲取和釋放原語的東西,這些原語和原語保持某些鎖屬性,例如,獨 占使用或單作者/多讀者,

   監視程式是一種機制,可確保在任何給定時間只有一個執行緒可以執行給定的代碼 段,可以使用鎖(和條件變數,允許執行緒等待或向其他執行緒發送滿足條件的通知) 來實作此功能,但它不僅僅是鎖,實際上,在Java情況下,不能直接訪問監視器 使用的實際鎖,(您不能說“ Object.lock()來阻止其他執行緒獲取它,就像使用Java Lock實體一樣,)

 

  簡而言之,如果要學究的話,“ monitor”實際上是比“ lock”更好的術語,用于描述 Java提供的特性,但是實際上,這兩個術語幾乎可以互換使用,

13、類加載器、反射、雙親委派

  (1)類加載器,其作用就是,負責將class檔案加載到記憶體中

    java中,先通過javacjava檔案編譯為class檔案, 然后使用ClassLoader類加載器加載class檔案到記憶體中,當一個類被使用時,就會加載到記憶體中,類加載的程序包括:加載,驗證,準備,初始化

  (2) 雙親委派模型

  每一個類都有一個相對應的類加載器,系統中的ClassLoader在協同作業會默認使用雙親委派模型,類加載的程序,是從父類到子類,類驗證的程序,是從子類到父類,也就是說,加載的時候,首先把該類委派給該父類加載器的loadClass()處理,因此所有的請求最終都應該傳送到頂層的啟動類加載器BootstrapClassLoader中,然后驗證,即在類加載的時候,系統就會首先判斷當前類是否被加載過,如果已經加載的類會直接回傳,否則都會嘗試加載,即當父類加載器無法處理時,都由自己來處理,當父類加載器為null時,會使用啟動類加載器BoostrapClassLoader作為父類加載器,

  雙親委派保證了java程式的穩定運行,避免了類的重復加載,

  除了BootstrapClassLoaderader其他類加載器均由Java實作且全部繼承于java.lang.ClassLoader,如果要自定義類加載器,就要繼承ClassLoader

  (3)反射

  反射是.class檔案加載進記憶體,把.class中的所有內容,封裝成 一個一個的物件的程序,在 程式中可以通過這些物件動態的完成方法的呼叫,成員變數的賦值,物件的創建!

  反射獲取Class物件的三種方式:

    iClass.forName(全類名)

    ii、類名.class

    iii、物件名.getClass();

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/225067.html

標籤:其他

上一篇:JVM全方位解讀(附面試題)

下一篇:Python 按分類樣本數占比生成并隨機獲取樣本資料

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more