[JVM]
雖然大部分情況下不需要我們直接對Java內(nèi)存方面進(jìn)行直接操作螺垢,但是了解其中的原理和調(diào)優(yōu)還是比較重要的蠢护,JVM中的總結(jié)如下:
[運(yùn)行數(shù)據(jù)區(qū)]
程序計(jì)數(shù)器
每個(gè)線程都有一個(gè)獨(dú)立的程序計(jì)數(shù)器蹄皱,是線程私有的等孵。
若是正在運(yùn)行一個(gè)方法辈赋,計(jì)數(shù)器記錄正在執(zhí)行虛擬機(jī)字節(jié)碼指令地址袋哼;
執(zhí)行的是native的方法纹安,計(jì)數(shù)器為空(Undefined)尤辱,不存在OutOfMemoryError虛擬機(jī)棧
線程私有,java方法執(zhí)行的內(nèi)存模型厢岂。
每個(gè)方法運(yùn)行的時(shí)候會創(chuàng)建一個(gè)棧幀光督,
棧幀存儲著 ->
(a)局部變量
用于存放方法參數(shù)和方法內(nèi)部定義的局部變量
包括基本數(shù)據(jù)類型boolean,byte,char,int,short,double,long.float,對象引用類型reference->不等于對象本身,returnAddress
(b)操作數(shù)棧
后入先出
(c)動態(tài)鏈接
(d)方法返回地址
返回方法被調(diào)用的位置
(e)一些額外的附加信息
規(guī)范里沒有提到的信息,例如與調(diào)試相關(guān)的信息本地方法棧
線程私有塔粒;
虛擬機(jī)使用的native的方法服務(wù)结借。java堆
java堆是被所以線程共享的一塊內(nèi)存區(qū)域,啟動時(shí)候創(chuàng)建卒茬。
所有對象實(shí)例和數(shù)據(jù)在堆上分配內(nèi)存船老。方法區(qū)
各個(gè)線程共享的內(nèi)存區(qū)域;
存儲java虛擬機(jī)加載的類信息,訪問修飾符圃酵,方法描述柳畔,常量,靜態(tài)變量郭赐,編譯后的代碼等數(shù)據(jù)薪韩。
(a)運(yùn)行的常量池
class文件->動態(tài)性
(b)直接內(nèi)存
例如NIO基于通道和緩沖區(qū)的I/O方式調(diào)用native
[垃圾回收]
- 引用計(jì)數(shù)算法
對象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用時(shí)候捌锭,計(jì)數(shù)器加1俘陷;引用失效時(shí),計(jì)數(shù)器值減1观谦;任何時(shí)候計(jì)數(shù)器的值為0拉盾,對象不再使用。 - 可達(dá)性分析算法
以GC Roots對象為起始點(diǎn)豁状,當(dāng)一個(gè)對象到達(dá)GCRoots沒有任何引用鏈的時(shí)候捉偏,證明對象不可達(dá)倒得。
作為GCRoots對象的包括:
(a) 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象
(b) 方法區(qū)中類靜態(tài)屬性引用的對象
(c) 方法區(qū)中常量引用的對象
(d) 本地方法棧中JNI(即一般說的native方法)引用的對象
- 標(biāo)記-清除算法
- 復(fù)制算法
內(nèi)存分為一塊較大的Eden和兩塊較小的Survivor空間,每次使用Eden和一塊Survivor空間夭禽;
回收時(shí)屎暇,將Eden和Survivor存活的對象一次性復(fù)制到另外一塊Survivor空間,清理用過的Eden和Survivor空間,默認(rèn)Eden和Survivor大小比例8:1 - 標(biāo)記-整理算法
[垃圾收集器]
- Serial收集器
新生代采取復(fù)制算法驻粟,暫停所有用戶線程;
老年代采取標(biāo)記整理算法凶异,暫停用戶所以線程 - ParNew收集器
Serial收集器的多線程版本
GC多線程蜀撑,新生代采取復(fù)制算法,暫停所有用戶線程剩彬;
老年代采取標(biāo)記整理算法酷麦,暫停用戶所以線程 - Parallel Scavenge收集器
CMS收集器盡可能縮短垃圾收集時(shí)用戶線程的停頓時(shí)間
Parallel Scavenge收集器目的達(dá)到一個(gè)可控制的吞吐量(吞吐量=運(yùn)行代碼時(shí)間/(運(yùn)行代碼時(shí)間+垃圾收集時(shí)間)) - Serial Old收集器
- Parallel Old收集器
- CMS收集器
以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。
采取標(biāo)記-清除算法 ->
(1)初始標(biāo)記
(2)并發(fā)標(biāo)記
(3)重新標(biāo)記
(4)并發(fā)清除
- G1收集器
面向服務(wù)端應(yīng)用的垃圾收集器
步驟 ->
(a)初始標(biāo)記
(b)并發(fā)標(biāo)記
(c)最終標(biāo)記
(d)篩選回收
[內(nèi)存分配]
-Xms20M,-Xmx20M,-Xmn10M -XX:SurvivorRatio=8,-XX:+PrintGCDetails
java堆大小為20M喉恋,不可擴(kuò)展沃饶,10M分配新生代,10M分配老年代
新生代的Eden區(qū)與一個(gè)Survivor區(qū)的空間比例為8:1
新生代的總可用空間(Eden區(qū)+1個(gè)Survivor區(qū)的總?cè)萘?
-XX:PermSize永久代
-XX:MaxPermSize永久代最大容量
存儲的是java的類信息轻黑,包括解析得到的方法糊肤、屬性、字段等等氓鄙。永久帶基本不參與垃圾回收馆揉。
在jdk1.8之前,通過-XX:PermSize=64m -XX:MaxPermSize=128m來調(diào)整永久代大小抖拦,
在jdk1.8之后升酣,永久代被移除,原本存儲在永久代的數(shù)據(jù)將存放在一個(gè)叫做元空間的本地內(nèi)存區(qū)域态罪,
通過 -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m來調(diào)整元空間大小
[類加載的時(shí)機(jī)]
生命周期
-> 加載
(a)通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流
(b)將這個(gè)字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu)
(c)在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對象噩茄,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口
-> 驗(yàn)證
(a)文件格式驗(yàn)證
(b)元數(shù)據(jù)驗(yàn)證
(c)字節(jié)碼驗(yàn)證
(d)符號引用驗(yàn)證
-> 準(zhǔn)備 -> 解析 -> 初始化 -> 使用 -> 卸載
[類加載器]
- 啟動類加載器(Bootstrap ClassLoader)
負(fù)責(zé)將存放<JAVA_HOME>\lib目錄中;
被-Xbootclasspath參數(shù)指定的路徑中(僅按照文件名識別复颈,如rt.jar) - 擴(kuò)展類加載器(Extension ClassLoader)
負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中
或者被java.ext.dirs系統(tǒng)變量所指定的路徑中所有的類庫 - 應(yīng)用類加載器(Application ClassLoader)
負(fù)責(zé)加載用戶類路徑ClassPath上所指定的類庫 - 自定義類加載器 UserClassLoader
如果一個(gè)類加載器收到了類加載的請求绩聘,請求委派給父類加載器,只有當(dāng)父類反饋無法完成券膀,子類嘗試加載君纫。
[Java內(nèi)存模型]
Java內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問規(guī)則,即在虛擬機(jī)中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)芹彬。
主內(nèi)存主要對應(yīng)與java堆里的對象實(shí)例數(shù)據(jù)部分蓄髓,而工作內(nèi)存則對應(yīng)虛擬機(jī)棧中的部分區(qū)域。
內(nèi)存間的交互
- lock鎖定
作用于主內(nèi)存的變量舒帮,它把一個(gè)變量標(biāo)識為一條線程獨(dú)占的狀態(tài)会喝。 - unlock(解鎖)
作用于主內(nèi)存的變量陡叠,把一個(gè)處于鎖定狀態(tài)的變量釋放出來,釋放后才能被其他線程鎖定肢执。 - read(讀取)
作用于主內(nèi)存的變量枉阵,將一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存,以便隨后的load動作使用预茄。 - load(載入)
作用于工作內(nèi)存的變量兴溜,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。 - use(使用)
作用于工作內(nèi)存的變量耻陕,它把一個(gè)變量的值傳遞給執(zhí)行引擎拙徽,每當(dāng)虛擬機(jī)遇到需要使用變量的值的字節(jié)碼指令時(shí)將會執(zhí)行這個(gè)操作。 - assign(賦值)
作用于工作內(nèi)存的變量诗宣,它把執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量膘怕,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作。 - store
作用于工作內(nèi)存的變量召庞,它把工作內(nèi)存內(nèi)存中一個(gè)變量的值傳送給主內(nèi)存中岛心,以便隨后的write操作使用。 - write(寫入)
作用于主內(nèi)存的變量篮灼,它把從store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中忘古。
volatile
- 此變量對所以線程可見(等線程A寫進(jìn)主內(nèi)存結(jié)束,線程B才開始讀取)
- 主內(nèi)存一致诅诱,工作內(nèi)存不能保持一致
- 禁止指令重排序優(yōu)化
內(nèi)存模型特征
- 原子性
- 可見性
變量修改后將新值同步回主內(nèi)存存皂,在變量讀取前從主內(nèi)存刷新變量值這種依賴主內(nèi)存作為傳遞媒介的方式來實(shí)現(xiàn)可見性。
volatile的特殊規(guī)則保證了新值能立即同步到主內(nèi)存逢艘,以及每次使用前立即從主內(nèi)存刷新旦袋。 - 有序性
[線程的實(shí)現(xiàn)]
- 使用內(nèi)核線程實(shí)現(xiàn)
內(nèi)核通過操縱調(diào)度器對線程進(jìn)行調(diào)度,并將線程的任務(wù)映射到各個(gè)處理器上它改。
內(nèi)核線程一種高級接口--輕量級進(jìn)程LWP疤孕,每個(gè)輕量級進(jìn)程由一個(gè)內(nèi)核線程支持。
用戶態(tài)和內(nèi)核態(tài)中來回切換央拖。
1:1 - 使用用戶線程實(shí)現(xiàn)
1:N - 使用用戶線程實(shí)現(xiàn)和使用用戶線程加輕量級進(jìn)程混合實(shí)現(xiàn)
N:M
[線程安全的同步方法]
- 互斥同步
互斥是實(shí)現(xiàn)同步的一種手段祭阀,臨界區(qū),互斥量鲜戒,信號量
阻塞同步 - 非阻塞同步
(a)測試并設(shè)置
(b)獲取并增加
(c)交換
(d)比較并交換
(e)加載鏈接/條件存儲
- 無同步方案
不涉及共享數(shù)據(jù)专控,不依賴堆的數(shù)據(jù)和公共系統(tǒng)資源
[鎖優(yōu)化]
- 自旋鎖和自適應(yīng)自旋
自旋--線程執(zhí)行一個(gè)忙循環(huán)-XX:+UseSpinning參數(shù)來開啟
自旋超過一定次數(shù)沒有成功獲得鎖,便使用傳統(tǒng)方式掛起線程,自旋次數(shù)默認(rèn)是10次遏餐,用戶可以使用參數(shù) -XX:PreBlockSpin更改 - 自適應(yīng)的自旋
由前一次在同一個(gè)鎖上的自旋時(shí)間時(shí)間及鎖的擁有者的狀態(tài)來決定,
如果同一個(gè)鎖對象伦腐,自旋剛剛成功過,允許自旋更長時(shí)間失都,很少成功柏蘑,則省略自旋這個(gè)過程幸冻。 - 鎖消除
虛擬機(jī)及時(shí)編譯器運(yùn)行的時(shí)候,對一些代碼要求同步咳焚,但是不可能存在數(shù)據(jù)競爭的進(jìn)行消除洽损。 - 鎖粗化
虛擬機(jī)探測到一串零碎的操作對同一個(gè)對象枷鎖,會把加鎖同步到整個(gè)操作序列外部革半。 - 輕量級鎖
無競爭的情況下使用CAS操作去消除同步使用的互斥量 - 偏向鎖
無競爭的情況下把整個(gè)同步都消除掉碑定,CAS操作不進(jìn)行。
-XX:+UseBiasedLocking
鎖對象第一次被線程獲取又官,會把對象頭標(biāo)志設(shè)置為“01”不傅,即偏向模式,同時(shí)用CAS操作記錄在對象的Mark Word之中赏胚,
如果CAS成功,以后每次持有偏向鎖的線程都不再進(jìn)行任何同步操作商虐。
如果有另一個(gè)線程來獲取觉阅,偏向鎖結(jié)束,根據(jù)鎖對象是否處于被鎖定的狀態(tài)秘车,撤銷偏向典勇,后續(xù)操作和輕量級鎖執(zhí)行類似。
缺點(diǎn):多個(gè)線程下訪問叮趴,并不適合割笙。
本文參考于《深入理解Java虛擬機(jī)》推薦大家看一看,會對自己收獲很大眯亦。