概要
- 基礎(chǔ)知識
- 內(nèi)存管理
- 執(zhí)行引擎
- 編譯與代碼優(yōu)化
- 高效并發(fā)
64位虛擬機(jī) VS 32位虛擬機(jī)
-
JVM虛擬機(jī) 性能 64位 < 32位
- 64位內(nèi)存占用更多
- 64位性能弱于32位虛擬機(jī)器:垃圾回收管理更大內(nèi)存,JIT的效率也有不同
普通對象指針壓縮,可以在64位虛擬機(jī)上優(yōu)化性能
-XX:+ UseCompressedOops
ps:以上是書本結(jié)論,但是物理機(jī)上的表現(xiàn)看,64位性能更好,一方面是指令更短,ALU(算術(shù)邏輯運(yùn)算器)和寄存器可以處理更大的整數(shù),特別是在高精度計(jì)算上64位更為卓越,32位寄存器有4G內(nèi)存空間的限制,64位內(nèi)存空間可以擴(kuò)展得更大。長期來看64位處理器和64位虛擬機(jī)會是趨勢,未來應(yīng)該還是首選64位物理機(jī),虛擬機(jī)也會是64位虛擬機(jī)。
編譯JDK
調(diào)試工具GDB
- todo:...
Java內(nèi)存區(qū)域 & 溢出異常
jvm結(jié)構(gòu)

-
【程序計(jì)數(shù)器】
- 線程私有,不會OOM,Native方法為Undefined,當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。
-
【JAVA虛擬機(jī)?!?/p>
- 每一個方法的執(zhí)行,就是一個棧幀的入棧到出棧
- 棧幀: 局部變量表,操作數(shù)棧,動態(tài)鏈接,方法出口
- 局部變量表:基本數(shù)據(jù)類型,對象引用,和returnAddress
- 長度64的long和double需要占用2個局部變量的空間(slot)其余的占用1個
- 局部變量表需要的空間在編譯器件確定,運(yùn)行期間不會改變
-
【本地方法棧】
- Native方法
-
【Java堆】
- 實(shí)例和數(shù)組在堆上分配
- JIT編譯器的發(fā)展和逃逸技術(shù)成熟【棧上分配 + 標(biāo)量替換】實(shí)例不一定在堆上創(chuàng)建。
- GC主要是針對的堆
- 收集器主要是:分代收集
- 新生代,老年代,...
-
【方法區(qū)】
- 線程共享,虛擬機(jī)加載的類信息,常量,靜態(tài)變量,即時編譯器編譯后的代碼數(shù)據(jù)
- 永久代
- 【運(yùn)行時常量池】是方法區(qū)的一部分,運(yùn)行時常量池和class文件常量池的區(qū)別是具備動態(tài)性。
其他:
- 【直接內(nèi)存】
* 不屬于運(yùn)行時數(shù)據(jù)區(qū)
* Nio中引入了channel和buffer的io方式,使用native函數(shù)庫直接分配堆外內(nèi)存
* 然后通過存儲在java堆中的DirectByteBuffer對象作為這塊內(nèi)存的引用操作
* 避免了java堆和native堆中來回復(fù)制數(shù)據(jù),提高了性能。
對象的創(chuàng)建過程N(yùn)ew
1 檢查在常量池中是否有一個類的符號引用,如果有,且已經(jīng)被加載,解析,初始化過,就返回,否則重新創(chuàng)建
2 分配內(nèi)存,指針碰撞(規(guī)整的空間移動一段距離),空閑列表(適用于不規(guī)整的空間)
- 注意分配空間如何線程安全:
- CAS 失敗重試
- 給每個線程預(yù)分配一段空間(TLAB),避免出現(xiàn)線程共享區(qū)域
3 必要的類設(shè)置:實(shí)例歸屬,元數(shù)據(jù)信息,hash碼,GC分代年紀(jì)等等
4 執(zhí)行init方法
對象的內(nèi)存布局
對象在內(nèi)存中布局分為3個區(qū)域:對象頭,實(shí)例數(shù)據(jù),對齊填充
- 對象頭:運(yùn)行時數(shù)據(jù) 和 類型指針
類型指針,指向它類元數(shù)據(jù)的指針,注意查找對象的數(shù)據(jù)不一定要經(jīng)過對象本身(之后會說)
ps:如果對象是一個java數(shù)組,還需要記錄數(shù)組長度 - 實(shí)例數(shù)據(jù),盡量是相同大小的分配在一起
- 對齊填充,保證數(shù)據(jù)是8的整數(shù)倍
對象的訪問定位
句柄 和 直接指針
-
句柄
image.png -
直接指針
image.png
- 直接指針只有2次指針訪問,速度快
- 句柄的好處是,reference穩(wěn)定,對象改變影響小
垃圾回收
標(biāo)記是否垃圾的算法
- 引用計(jì)數(shù)
- 缺陷:互相引用的時候不會被回收
- 可達(dá)性分析,GCroot向下搜索
- 可作為GCroot的對象
- 虛擬機(jī)棧的棧幀 中的本地變量表 的引用的 對象
- 方法區(qū)中的類靜態(tài)屬性引用的對象
- 方法區(qū)中的常量引用的對象
- 本地方法棧中JNI(即Native方法)引用的對象
- 可作為GCroot的對象
引用
- 強(qiáng)引用:引用還在,垃圾回收不會回收
- 軟引用:內(nèi)存溢出前回收
- 弱引用:每次垃圾回收都回收
- 虛引用 Phantom Reference:被垃圾回收的時候可以收到系統(tǒng)通知
回收
- 1 可達(dá)性分析之后先標(biāo)記可以回收
- 2 如果沒有覆蓋finalize()方法,或者finalize()被調(diào)用過,則沒有必要執(zhí)行
- 3 如果有必要執(zhí)行,進(jìn)入F-Queue隊(duì)列中,之后被JVM創(chuàng)建的一個線程回收
注意,finalize()運(yùn)行代價(jià)高,不保證調(diào)用順序,所以不適合用來關(guān)閉外部資源,應(yīng)該用try-finally來關(guān)閉資源。
回收方法區(qū)(永久代)
- 廢棄常量
- 無用的類
- 類的所有實(shí)例被回收
- 加載類的ClassLoader被回收
- 類對應(yīng)的java.lang.Class對象沒有在任何地方被引用,也無法通過反射訪問類的方法
GC的算法
- 標(biāo)記清除
- 復(fù)制:回收新生代
- 標(biāo)記整理
- 分代收集
GC的stop the world:
- 大部分GC在做垃圾回收都會掛起所有用戶線程,保證回收的數(shù)據(jù)一致性(比如 Copying這種算法)
- 枚舉根結(jié)點(diǎn),一定會stop the world
- 使用安全點(diǎn),gc的時候讓線程運(yùn)行到安全點(diǎn),只有到達(dá)了安全點(diǎn)才能進(jìn)行GC
- 如果線程沒有cpu時間,也無法走到安全點(diǎn),那么就要用安全區(qū)域來解決,一段代碼中,引用關(guān)系不變化,這個區(qū)域的任意地方GC都是安全的。
性能要求高的環(huán)境難以接受,所以要減少頻繁的GC,CMS分為幾個階段:
*【initial Mark】
- Concurrent Mark
- 【ReMark】
- Concurrent sweep
initial Mark和Remark會Stop the world. Concurrent Mark和Concurrent Sweep會和用戶線程一起運(yùn)行。雖然CMS減少了stop the world的次數(shù),不可避免地讓整體GC的時間拉長了

不同的垃圾收集器,沒有一個通用的,HotSpot實(shí)現(xiàn)了很多:
新生代的:大多是復(fù)制算法
- Serial:單線程收集器,有停頓,簡單,適用于允許停頓的客戶端程序。
- ParNew:Serial的多線程版本,性能優(yōu),除了Serial之外,只有他能和CMS配合工作。在JDK5之后,老年代選用CMS,新生代就只能用Serial和ParNew了。單核環(huán)境中,ParNew的性能不如Serial(線程交錯的開銷)
- 【Parallel Scavenge】,關(guān)注的不是減少停頓時間,而是達(dá)到一個吞吐量(代碼運(yùn)行時間/(代碼運(yùn)行時間 + GC時間)),所以指定的參數(shù)也是垃圾收集的最大停頓時間,以及吞吐量大小
老年代的:大多是標(biāo)記清除 or 標(biāo)記整理算法
- Serial Old
- Praallel Old
- 【CMS】,并發(fā),標(biāo)記清除
CMS:
- 幾個階段:初始標(biāo)記,并發(fā)標(biāo)記,重新標(biāo)記,并發(fā)回收
- 優(yōu)點(diǎn):性能,停頓少
- 缺點(diǎn):標(biāo)記清除產(chǎn)生碎片多,無法處理浮動垃圾,并發(fā)清理階段可能還產(chǎn)生新的垃圾,會出現(xiàn)Concurrent Mode Failure,導(dǎo)致額外產(chǎn)生一次GC
G1收集器 VS CMS
- G1更代表未來
- G1過程:初始標(biāo)記,并發(fā)標(biāo)記,最終標(biāo)記,篩選回收
- G1分代收集,老年代和年輕代都可以用
- G1 標(biāo)記-整理,碎片少
- G1 可預(yù)測的停頓,垃圾收集時間不超過xx毫秒有可控參數(shù)
對象分配策略
上圖還需要補(bǔ)充一個點(diǎn)“空間分配擔(dān)?!?/p>
第四章 故障 & 性能監(jiān)控工具
工具列表
- jps:查看進(jìn)程,主類
-q 本地虛擬機(jī)唯一id
-l 主類全名
-v 啟動的JVM參數(shù)
-m 給主類的參數(shù)
- jstat:虛擬機(jī)統(tǒng)計(jì)信息監(jiān)控
- class 類裝載,總空間等
- gc 堆的狀況,各個區(qū)域
- compiler jit編譯過的方法和耗時
- jhat:可視化
- jinfo:查看和調(diào)整JVM配置信息
- jstack:查看棧
- jmap: 查看堆
- dump 堆快照
- heap 堆詳情
- histo 堆統(tǒng)計(jì)
調(diào)優(yōu)經(jīng)驗(yàn)
- 深夜觸發(fā)定時任務(wù)做 fullGC,保持內(nèi)存在一個穩(wěn)定水平
- JDK 64 在堆內(nèi)存溢出的時候 dump 會有十幾個G的文件,大文件的dump和分析都比較困難。
類文件的結(jié)構(gòu)
傳統(tǒng)編譯器: 類文件 --> 編譯 --> 本地機(jī)器碼
java: 類文件(.java)--> 編譯 --> 字節(jié)碼 (.class)--> jvm
構(gòu)成
- 類文件構(gòu)成
- magic
- magic_version
- major_version
- constant_pool_count
- constant_pool
- access_flags
- this_class
- super_class
- interfaces_count
- interfaces
- fields_count
- fields
- methods_count
- methods
- attributes_count
- attributes
指令集
- 指令集
類加載機(jī)制
- 類加載過程
準(zhǔn)備階段
1 如果是類變量,會在解析階段賦初始化的0值。
比如 static int value=123,這個階段value是0
分配在【方法區(qū)】
而value= 123的putstatic是在編譯后,初始化階段,放在類構(gòu)造器<clinit>()方法中
如果是實(shí)例變量,會在對象實(shí)例化的時候,隨著對象一起分配在java堆中
對于常量ConstantValue中有值
static final int value = 123
會在編譯的時候?yàn)関alue生成ConstantValue屬性,在準(zhǔn)備階段value就會變?yōu)?23
- 類的初始化過程與類的實(shí)例化過程的異同?
類的初始化是指:
類加載過程中的初始化階段對類變量按照程序猿的意圖進(jìn)行賦值的過程
類的實(shí)例化是指:
在類完全加載到內(nèi)存中后創(chuàng)建對象的過程。
類實(shí)例化的一般過程是:
父類的類構(gòu)造器<clinit>()
-> 子類的類構(gòu)造器<clinit>()
-> 父類的成員變量和實(shí)例代碼塊
-> 父類的構(gòu)造函數(shù)
-> 子類的成員變量和實(shí)例代碼塊
-> 子類的構(gòu)造函數(shù)。
編譯優(yōu)化
編譯器,解釋器,通常jvm采用的是混合模式,但是可以通過參數(shù)來指定編譯或者解釋。
對于熱點(diǎn)代碼(采樣探測 or 計(jì)數(shù)器),會被即時編譯器編譯,
即時編譯的優(yōu)化有若干,這里忽略
其他優(yōu)化策略:
- 公共子表達(dá)式消除
- 數(shù)組邊界檢查消除
- 方法內(nèi)聯(lián)(減少引用)
- 逃逸分析:對象的棧上分配,同步消除,標(biāo)量替換





