java中就虛擬機是其他語言撰寫的(C語言+匯編語言,因此,JVM最常出現的攻擊就是buffer overflow),如javac命令等,而java api是java寫的,大多開源在openjdk,jdk中有一個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運行時資料區,JVM的NIO方法可以分配堆外記憶體如使用 DirectByteBuffer
2、堆疊的例外總結
(1)堆記憶體溢位OutOfMemoryError:java heap space
產生原因:java堆用于存盤物件實體,只要不斷的創建物件,并保證GC roots到物件間有可達路徑避免這些物件的GC,那么,當物件數量到達堆的最大容量限制后就會產生OOM
解決辦法:
- 通過引數 -XX:HeapDumpOnOutOfMemoryError 可以讓虛擬機在記憶體溢位例外時Dump當前記憶體堆轉儲快照
- 通過記憶體映像分析工具(如:Eclipse Memory Analyzer)對Dump出的堆轉儲快照分析,判斷是記憶體泄露還是記憶體溢位
- 如果是記憶體泄露:通過工具查看泄露物件的型別資訊和它們到 GC Roots 的參考鏈資訊,分析GC收集器無法自動回收它們的原因,定位記憶體泄露的代碼位置
- 如果是記憶體溢位:檢查堆引數 -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,初始堆大小,Server端JVM最好將-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,即Eden為8/10,兩個survivor分別為1/10,其中一個survivor閑置,用于復制,所以年輕代實際可用的記憶體 大小為-Xmn設定值的9/10,Eden設定的太大的話,會導致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繼續等待下一次GC,Survivor區每熬過一次MinorGC,就將物件的年齡+1,當物件的年齡到達某個值時(默認時15,可用通過引數-XX:MaxTenuringThreshold 來設定),這些物件就會成為老年代
(2) MajorGC的程序:老年代的回收,不會那么頻繁,老年代使用的回收演算法是標記-清除或標記-整理演算法,標記-清除演算法會產生記憶體碎片,即不連續的空間,如果此時,有大的物件進來,記憶體中沒有足夠的連續空間時,會提前觸發FullGC(這是一個優化點),一次FullGC的時間要比一次MinorGC的時間長,當年老代也裝不下,就會拋出OOM(Out Of Memory)例外 (3) Full GC的觸發:老年代滿了而無法容納更多的物件,會觸發Full GC,Full GC 清理整個記憶體堆,包括年輕代和老年代,
6、GC的回收演算法
(1) 標記-清除演算法,缺點是容易產生碎片,且效率不高,標記程序和清除程序效率都不高
標記-清除演算法的程序,分為兩個程序,標記程序和清除程序
① 標記程序,遍歷所有的GC-roots,然后將所有GC-roots可達的物件標記為存活的物件(記為1)
② 清除程序,遍歷堆中的所有物件,將沒有標記的物件全部清除掉(沒有標記過的,記為0)
③ 清除過后,被標記過的物件留下,標志位重新歸0
以下是標記-清除演算法的圖示(圖片來自于網路)

(2) 復制演算法,用于年輕代,需要額外的空間來進行復制操作
復制演算法的程序,就是把記憶體分為2塊等同大小的記憶體空間(A和B),使用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,指定G1中Rigion的大小,如果未設定,默認將堆記憶體平均分為 2048 份
-XX:MaxGCPauseMillis=n,設定GC時最大暫停時間,這個目標不一定能滿足,JVM會盡最大努力實作它,不建議設定的過小(<50ms)
-XX:InitiatingHeapOccupancyPercent=n,觸發G1啟動 Mixed GC,表示垃圾物件在整個G1 堆記憶體空間的占比
避免使用 -Xmn 或 -XX:NewRatio等其他顯式設定年輕代大小的選項,固定年 輕代大小,會覆寫暫停時間目標
9、JVM的記憶體分析工具
JVM自帶的記憶體分析小工具:jconsole、jhat、jmap、jstack、jstat、jstatd、jvisualvm
linux工具:pidstat、vmstat、iostat
eclipse的分析工具:mat
收費工具:Jporfiler,yourkit
(1) jconsole,jvm自帶記憶體分析工具,位于jdk的bin目錄下,它提供了圖形界面,可以查看到被監控的jvm的記憶體資訊,執行緒資訊,類加載資訊,MBean資訊,
(2) jhat,jvm自帶記憶體分析工具,位于jdk的bin目錄下,jdk6+版本自帶,能夠分析dump檔案,執行 jhat -J -Xmx512m [file] ,file就是dump檔案路徑,
(3) jmap,jvm自帶記憶體分析工具,位于jdk的bin目錄下,傾向于分析jvm記憶體中物件資訊,jmap -histo <pid>在螢屏上顯示出指定pid的jvm記憶體狀況,太簡單,
jmap -dump:file=c:\dump.txt 340 匯出dump檔案,用專門的dump分析工具分析,
(4) jstack,jvm自帶記憶體分析工具,位于jdk的bin目錄下,會顯示執行緒優先級,執行緒ID,native執行緒ID,執行緒堆疊起始地址
(5) jstat,jvm自帶記憶體分析工具,位于jdk的bin目錄下,傾向于分析jvm記憶體的gc情況,常用引數-gcutil,這個引數的作用不斷的顯示當前指定的jvm記憶體的垃圾收集的資訊, jstat -gcutil 340 10000,這個命令是每個10秒鐘輸出一次jvm的gc資訊,10000指的是間隔時間為10000毫秒,
(6) jstatd,jvm自帶記憶體分析工具,位于jdk的bin目錄下,一個RMI的server,它可以監控Hotspot的JVM的啟動和結束,同時提供介面可以讓遠程機器連接到JVM, 比如 jps jstat都可以通過jstatd來遠程觀察JVM的運行情況,
(7) jvisualvm,jvm自帶記憶體分析工具,位于jdk的bin目錄下,JDK6 update 7之后推出,,java可視化虛擬機,它不但提供了jconsole類似的功能,還提供了jvm記憶體和cpu實時診斷,還有手動dump出jvm記憶體情況,手動執行gc,和jconsole一樣,運行jviusalvm,在jdk的bin目錄下執行jvisualvm,windows下是jvisualvm.exe,linux和unix下是jvisualvm.sh,
(8) pidstat,linux系統下使用,需要安裝,yum install sysstat,要查看Linux下面行程、行程組、執行緒的資源消耗的統計資訊,可以使用pidstat,它可以收集并報告行程的統計資訊,
(9) vmstat,linux系統下使用,需要安裝,vmstat是Virtual Meomory Statistics(虛擬記憶體統計)的縮寫,可對作業系統的虛擬記憶體、行程、CPU活動進行監控,是對系統的整體情況進行統計,不足之處是無法對某個行程進行深入分析,
(10) iostat,linux系統下使用,需要安裝,yum install sysstat,iostat是I/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:存盤物件自身的運行時資料,例如:哈希碼HashCode、GC分代年齡、鎖狀態標志、執行緒持有的鎖、偏向執行緒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”
下圖描述了物件、物件的監視器、同步佇列和執行執行緒之間的關系,

從上圖中可以看到,任意執行緒對Object(Object由synchronized保護)的訪問,首先 要獲得Object的監視器,如果獲取失敗,執行緒進入同步佇列,執行緒狀態變為BLOCKED, 當訪問Object的前驅(獲得了鎖的執行緒)釋放了鎖,則該釋放操作喚醒阻塞在同步隊 列中的執行緒,使其重新嘗試對監視器的獲取,
那么,物件的監視器到底是做什么的,用在哪里,起什么作用?
為什么使用術語“監視器”而不是“鎖定”?嚴格來說,確實不同,“鎖”是指具有獲取和釋放原語的東西,這些原語和原語保持某些鎖屬性,例如,獨 占使用或單作者/多讀者,
“監視程式”是一種機制,可確保在任何給定時間只有一個執行緒可以執行給定的代碼 段,可以使用鎖(和“條件變數”,允許執行緒等待或向其他執行緒發送滿足條件的通知) 來實作此功能,但它不僅僅是鎖,實際上,在Java情況下,不能直接訪問監視器 使用的實際鎖,(您不能說“ Object.lock()”來阻止其他執行緒獲取它,就像使用Java Lock實體一樣,)
簡而言之,如果要學究的話,“ monitor”實際上是比“ lock”更好的術語,用于描述 Java提供的特性,但是實際上,這兩個術語幾乎可以互換使用,
13、類加載器、反射、雙親委派
(1)類加載器,其作用就是,負責將class檔案加載到記憶體中
java中,先通過javac將java檔案編譯為class檔案, 然后使用ClassLoader類加載器加載class檔案到記憶體中,當一個類被使用時,就會加載到記憶體中,類加載的程序包括:加載,驗證,準備,初始化
(2) 雙親委派模型
每一個類都有一個相對應的類加載器,系統中的ClassLoader在協同作業會默認使用雙親委派模型,類加載的程序,是從父類到子類,類驗證的程序,是從子類到父類,也就是說,加載的時候,首先把該類委派給該父類加載器的loadClass()處理,因此所有的請求最終都應該傳送到頂層的啟動類加載器BootstrapClassLoader中,然后驗證,即在類加載的時候,系統就會首先判斷當前類是否被加載過,如果已經加載的類會直接回傳,否則都會嘗試加載,即當父類加載器無法處理時,都由自己來處理,當父類加載器為null時,會使用啟動類加載器BoostrapClassLoader作為父類加載器,
雙親委派保證了java程式的穩定運行,避免了類的重復加載,
除了BootstrapClassLoaderader其他類加載器均由Java實作且全部繼承于java.lang.ClassLoader,如果要自定義類加載器,就要繼承ClassLoader,
(3)反射
反射是把.class檔案加載進記憶體,把.class中的所有內容,封裝成 一個一個的物件的程序,在 程式中可以通過這些物件動態的完成方法的呼叫,成員變數的賦值,物件的創建!
反射獲取Class物件的三種方式:
i、Class.forName(全類名)
ii、類名.class
iii、物件名.getClass();
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/225067.html
標籤:其他
上一篇:JVM全方位解讀(附面試題)
