取百家之長吱殉,僅用作個(gè)人的學(xué)習(xí)之用掸冤,侵刪。
jvm內(nèi)存 jmm內(nèi)存模型 類的加載 GC
java內(nèi)存模型
匿名對(duì)象只使用一次友雳,使用完將進(jìn)行GC
instance = new Singleton();
這條語句實(shí)際上包含了三個(gè)操作:1.分配對(duì)象的內(nèi)存空間稿湿;2.初始化對(duì)象;3.設(shè)置instance指向剛分配的內(nèi)存地址押赊。但由于存在重排序的問題缎罢,可能有以下的執(zhí)行順序:
重排序不會(huì)影響單線程環(huán)境的執(zhí)行結(jié)果,但是會(huì)破壞多線程的執(zhí)行語義考杉。
原子性是指一個(gè)操作是不可中斷的策精,要么全部執(zhí)行成功要么全部執(zhí)行失敗,有著“同生共死”的感覺崇棠。及時(shí)在多個(gè)線程一起執(zhí)行的時(shí)候咽袜,一個(gè)操作一旦開始,就不會(huì)被其他線程所干擾枕稀。
可見性是指當(dāng)一個(gè)線程修改了共享變量的值询刹,其他線程能夠立即得知這個(gè)修改。volatile變量可以做到這一點(diǎn)萎坷。
有序性是指如果在本線程內(nèi)觀察凹联,所有的操作都是有序的;如果在一個(gè)線程觀察另一個(gè)線程哆档,所有的操作都是無序的蔽挠。volatile強(qiáng)制主內(nèi)存讀寫同步,包含禁止指令重排序的語義瓜浸,其具有有序性澳淑。
注意:共享數(shù)據(jù)的訪問權(quán)限必須定義為private
編譯
源代碼文件*.java -> 詞法分析器 -> tokens流 -> 語法分析器 -> 語法樹/抽象語法樹 -> 語義分析器 -> 注解抽象語法樹 -> 字節(jié)碼生成器 ->JVM字節(jié)碼文件*.class
類(方法區(qū):class被加載到的內(nèi)存區(qū)域)
加載:通過完全限定名---字節(jié)碼文件---class對(duì)象
驗(yàn)證:確保不會(huì)危害虛擬機(jī)安全
準(zhǔn)備:為static修飾的變量分配內(nèi)存,初始值0插佛,null杠巡。具體值在初始化階段設(shè)置。而final修飾的變量在編譯時(shí)分配內(nèi)存雇寇。
解析:常量池中的符號(hào)引用替換為直接引用----直接指向目標(biāo)的指針或相對(duì)偏移量氢拥。
初始化:靜態(tài)塊的執(zhí)行和靜態(tài)變量的賦值。觸發(fā)條件:1锨侯、實(shí)例化 2嫩海、訪問靜態(tài)方法、變量 3识腿、Class.forName() 4出革、子類被初始化(如果父類沒有被初始化,先初始化父類)
把類委托給父類加載器去執(zhí)行渡讼,如果父類加載器可以加載骂束,就繼續(xù)向上委托,如果不能加載則自己加載成箫,這樣可以避免核心API被惡意篡改展箱。
類加載引出的執(zhí)行順序問題
ps:沒有 static 關(guān)鍵字修飾的(如:實(shí)例變量[非靜態(tài)變量]混驰、非靜態(tài)代碼塊)初始化實(shí)際上是會(huì)被提取到類的構(gòu)造器中被執(zhí)行的,但是會(huì)比類構(gòu)造器中的代碼塊優(yōu)先執(zhí)行到皂贩,其也是按順序從上到下依次被執(zhí)行栖榨。也就是{這是構(gòu)造代碼塊}會(huì)比public B(){這是構(gòu)造方法}先執(zhí)行。
方法區(qū)(Method Area)
? ? ? 方法區(qū)存放了要加載的類的信息(如類名明刷、修飾符等)婴栽、靜態(tài)變量、構(gòu)造函數(shù)辈末、final定義的常量愚争、類中的字段和方法等信息。方法區(qū)是全局共享的挤聘,在一定條件下也會(huì)被GC轰枝。當(dāng)方法區(qū)超過它允許的大小時(shí),就會(huì)拋出OutOfMemory:PermGen Space異常组去。
? ? ? 在Hotspot虛擬機(jī)中鞍陨,這塊區(qū)域?qū)?yīng)持久代(Permanent Generation),一般來說从隆,方法區(qū)上執(zhí)行GC的情況很少湾戳,因此方法區(qū)被稱為持久代的原因之一,但這并不代表方法區(qū)上完全沒有GC广料,其上的GC主要針對(duì)常量池的回收和已加載類的卸載砾脑。在方法區(qū)上進(jìn)行GC艾杏,條件相當(dāng)苛刻而且困難韧衣。
? ? ? 運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分,用于存儲(chǔ)編譯器生成的常量和引用购桑。一般來說畅铭,常量的分配在編譯時(shí)就能確定,但也不全是勃蜘,也可以存儲(chǔ)在運(yùn)行時(shí)期產(chǎn)生的常量硕噩。比如String類的intern()方法,作用是String類維護(hù)了一個(gè)常量池缭贡,如果調(diào)用的字符”hello”已經(jīng)在常量池中炉擅,則直接返回常量池中的地址辉懒,否則新建一個(gè)常量加入池中,并返回地址谍失。
堆區(qū)(Heap)
? ? ? 堆區(qū)是GC最頻繁的眶俩,也是理解GC機(jī)制最重要的區(qū)域。堆區(qū)由所有線程共享快鱼,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建颠印。堆區(qū)主要用于存放對(duì)象實(shí)例及數(shù)組,所有new出來的對(duì)象都存儲(chǔ)在該區(qū)域抹竹。
棧(虛擬機(jī)棧)(VM Stack)后進(jìn)先出
(線程操作线罕,存放方法參數(shù))
執(zhí)行的時(shí)候,每個(gè)線程都有一個(gè)Java棧窃判,當(dāng)前執(zhí)行的棧稱為當(dāng)前棧钞楼。一個(gè)Java棧調(diào)用多個(gè)方法,則會(huì)push很多個(gè)棧幀兢孝,當(dāng)前活動(dòng)的棧幀稱為當(dāng)前棧幀窿凤。當(dāng)前棧幀執(zhí)行完畢之后,會(huì)把執(zhí)行結(jié)果(如果有)壓入到調(diào)用它的那個(gè)棧幀的操作數(shù)棧中跨蟹,作為上一個(gè)棧幀的一個(gè)中間處理結(jié)果被調(diào)用雳殊,然后就會(huì)被pop出去。當(dāng)所有調(diào)用的方法執(zhí)行結(jié)束后窗轩,棧幀也就都pop掉沒有了夯秃。
例如:執(zhí)行代碼
? 虛擬機(jī)棧占用的是操作系統(tǒng)內(nèi)存,每個(gè)線程對(duì)應(yīng)一個(gè)虛擬機(jī)棧痢艺,它是線程私有的仓洼,生命周期和線程一樣,每個(gè)方法被執(zhí)行時(shí)產(chǎn)生一個(gè)棧幀(Statck Frame)堤舒,棧幀用于存儲(chǔ)局部變量表色建、動(dòng)態(tài)鏈接、操作數(shù)和方法出口等信息舌缤,當(dāng)方法被調(diào)用時(shí)箕戳,棧幀入棧,當(dāng)方法調(diào)用結(jié)束時(shí)国撵,棧幀出棧陵吸。
? ? ? 局部變量表中存儲(chǔ)著方法相關(guān)的局部變量,包括各種基本數(shù)據(jù)類型及對(duì)象的引用地址等介牙,因此他有個(gè)特點(diǎn):內(nèi)存空間可以在編譯期間就確定壮虫,運(yùn)行時(shí)不再改變。
? ? ? 虛擬機(jī)棧定義了兩種異常類型:StackOverFlowError(棧溢出SOF)和OutOfMemoryError(內(nèi)存溢出)环础。如果線程調(diào)用的棧深度大于虛擬機(jī)允許的最大深度囚似,則拋出StackOverFlowError剩拢;不過大多數(shù)虛擬機(jī)都允許動(dòng)態(tài)擴(kuò)展虛擬機(jī)棧的大小,所以線程可以一直申請(qǐng)棧谆构,直到內(nèi)存不足時(shí)裸扶,拋出OutOfMemoryError框都。
本地方法棧(Native Method Stack)
native是一個(gè)計(jì)算機(jī)函數(shù)搬素,一個(gè)Native Method就是一個(gè)Java調(diào)用非Java代碼的接口。方法的實(shí)現(xiàn)由非Java語言實(shí)現(xiàn)魏保,比如C或C++熬尺。
native關(guān)鍵字說明其修飾的方法是一個(gè)原生態(tài)方法,方法對(duì)應(yīng)的實(shí)現(xiàn)不是在當(dāng)前文件谓罗,而是在用其他語言(如C和C++)實(shí)現(xiàn)的文件中粱哼。Java語言本身不能對(duì)操作系統(tǒng)底層進(jìn)行訪問和操作,但是可以通過JNI接口調(diào)用其他語言來實(shí)現(xiàn)對(duì)底層的訪問檩咱。
JNI是Java本機(jī)接口(Java Native Interface)揭措,是一個(gè)本機(jī)編程接口,它是Java軟件開發(fā)工具箱(Java Software Development Kit刻蚯,SDK)的一部分绊含。JNI允許Java代碼使用以其他語言編寫的代碼和代碼庫。Invocation API(JNI的一部分)可以用來將Java虛擬機(jī)(JVM)嵌入到本機(jī)應(yīng)用程序中炊汹,從而允許程序員從本機(jī)代碼內(nèi)部調(diào)用Java代碼躬充。
? ? ? 本地方法棧用于支持native方法的執(zhí)行,存儲(chǔ)了每個(gè)native方法的執(zhí)行狀態(tài)讨便。本地方法棧和虛擬機(jī)棧他們的運(yùn)行機(jī)制一致充甚,唯一的區(qū)別是,虛擬機(jī)棧執(zhí)行Java方法霸褒,本地方法棧執(zhí)行native方法伴找。在很多虛擬機(jī)中(如Sun的JDK默認(rèn)的HotSpot虛擬機(jī)),會(huì)將虛擬機(jī)棧和本地方法棧一起使用废菱。
程序計(jì)數(shù)器(Program Counter Register)
? ? ? 程序計(jì)數(shù)器是一個(gè)很小的內(nèi)存區(qū)域技矮,不在RAM上,而是直接劃分在CPU上昙啄,程序猿無法操作它穆役,它的作用是:JVM在解釋字節(jié)碼(.class)文件時(shí),存儲(chǔ)當(dāng)前線程執(zhí)行的字節(jié)碼行號(hào)梳凛,只是一種概念模型耿币,各種JVM所采用的方式不一樣。字節(jié)碼解釋器工作時(shí)韧拒,就是通過改變程序計(jì)數(shù)器的值來取下一條要執(zhí)行的指令淹接,分支十性、循環(huán)、跳轉(zhuǎn)等基礎(chǔ)功能都是依賴此技術(shù)區(qū)完成的塑悼。
? ? ? 每個(gè)程序計(jì)數(shù)器只能記錄一個(gè)線程的行號(hào)劲适,因此它是線程私有的。
? ? ? 如果程序當(dāng)前正在執(zhí)行的是一個(gè)java方法厢蒜,則程序計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令地址霞势,如果執(zhí)行的是native方法,則計(jì)數(shù)器的值為空斑鸦,此內(nèi)存區(qū)是唯一不會(huì)拋出OutOfMemoryError的區(qū)域(OOM異常)愕贡。
GC(Garbage Collection)
一、哪些需要GC
a.該類的所有實(shí)例都已經(jīng)被回收巷屿;
b.加載該類的ClassLoad已經(jīng)被回收固以;
c.該類對(duì)應(yīng)的反射類java.lang.Class對(duì)象沒有被任何地方引用。
在上面介紹的五個(gè)內(nèi)存區(qū)域中嘱巾,有3個(gè)是不需要進(jìn)行垃圾回收的:
本地方法棧憨琳、程序計(jì)數(shù)器、虛擬機(jī)棧旬昭。
因?yàn)樗麄兊纳芷谑呛途€程同步的篙螟,隨著線程的銷毀,他們占用的內(nèi)存會(huì)自動(dòng)釋放稳懒。
所以闲擦,只有方法區(qū)和堆區(qū)需要進(jìn)行垃圾回收,回收的對(duì)象就是那些不存在任何引用的對(duì)象场梆。
可達(dá)性檢測(cè)(哪些需要清理)
? 經(jīng)典的引用計(jì)數(shù)算法墅冷,
每個(gè)對(duì)象添加到引用計(jì)數(shù)器,每被引用一次或油,計(jì)數(shù)器+1含懊,失去引用停忿,計(jì)數(shù)器-1绊起,當(dāng)計(jì)數(shù)器在一段時(shí)間內(nèi)為0時(shí)芍阎,即認(rèn)為該對(duì)象可以被回收了。但是這個(gè)算法有個(gè)明顯的缺陷:當(dāng)兩個(gè)對(duì)象相互引用辖佣,但是二者都已經(jīng)沒有作用時(shí)霹抛,理應(yīng)把它們都回收,但是由于它們相互引用卷谈,不符合垃圾回收的條件(循環(huán)引用)杯拐,所以就導(dǎo)致無法處理掉這一塊內(nèi)存區(qū)域。
因此,Sun的JVM并沒有采用這種算法端逼,而是采用一個(gè)叫——根搜索算法朗兵,
基本思想是:從一個(gè)叫GC Roots的根節(jié)點(diǎn)出發(fā),向下搜索顶滩,如果一個(gè)對(duì)象不能達(dá)到GC Roots的時(shí)候余掖,說明該對(duì)象不再被引用,可以被回收礁鲁。如上圖中的Object5盐欺、Object6、Object7救氯,雖然它們?nèi)齻€(gè)依然相互引用找田,但是它們其實(shí)已經(jīng)沒有作用了歌憨,這樣就解決了引用計(jì)數(shù)算法的缺陷着憨。
GC Roots主要指:
a.虛擬機(jī)棧(棧楨中的本地變量表)中的引用的對(duì)象
b.方法區(qū)中的類靜態(tài)屬性引用的對(duì)象
c.方法區(qū)中的常量引用的對(duì)象
d.本地方法棧中JNI的引用的對(duì)象
? ? ? 補(bǔ)充概念,在JDK1.2之后引入了四個(gè)概念:強(qiáng)引用务嫡、軟引用甲抖、弱引用、虛引用心铃。
? ? ? 強(qiáng)引用:new出來的對(duì)象都是強(qiáng)引用准谚,GC無論如何都不會(huì)回收,即使拋出OOM異常去扣。
? ? ? 軟引用:只有當(dāng)JVM內(nèi)存不足時(shí)才會(huì)被回收柱衔。
? ? ? 弱引用:只要GC,就會(huì)立馬回收,不管內(nèi)存是否充足愉棱。
? ? ? 虛引用:可以忽略不計(jì)唆铐,JVM完全不會(huì)在乎虛引用,你可以理解為它是來湊數(shù)的奔滑,湊夠”四 大天王”艾岂。它唯一的作用就是做一些跟蹤記錄,輔助finalize函數(shù)的使用朋其。
二王浴、如何GC
內(nèi)存主要被分為三塊:新生代(Youn Generation)、舊生代(Old Generation)梅猿、持久代(Permanent Generation)氓辣。三代的特點(diǎn)不同,造就了他們使用的GC算法不同袱蚓,新生代適合生命周期較短钞啸,快速創(chuàng)建和銷毀的對(duì)象,舊生代適合生命周期較長的對(duì)象,持久代在Sun Hotpot虛擬機(jī)中就是指方法區(qū)(有些JVM根本就沒有持久代這一說法)爽撒。
新生代(Youn Generation):大致分為Eden區(qū)和Survivor區(qū)入蛆,Survivor區(qū)又分為大小相同的兩部分:FromSpace和ToSpace。新建的對(duì)象都是從新生代分配內(nèi)存硕勿,Eden區(qū)不足的時(shí)候哨毁,會(huì)把存活的對(duì)象轉(zhuǎn)移到Survivor區(qū)。當(dāng)新生代進(jìn)行垃圾回收時(shí)會(huì)出發(fā)Minor GC(也稱作Youn GC)源武。
新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過參數(shù) –XX:NewRatio 來指定 )
默認(rèn)的扼褪,Eden : from : to = 8 : 1 : 1 ( 可以通過參數(shù) –XX:SurvivorRatio 來設(shè)定 ),即: Eden = 8/10 的新生代空間大小粱栖,from = to = 1/10 的新生代空間大小话浇。
? ? ? 舊生代(Old Generation):舊生代用于存放新生代多次回收依然存活的對(duì)象,如緩存對(duì)象闹究。當(dāng)舊生代滿了的時(shí)候就需要對(duì)舊生代進(jìn)行回收幔崖,舊生代的垃圾回收稱作Major GC(也稱作Full GC)。
? ? ? 持久代(Permanent Generation):在Sun 的JVM中就是方法區(qū)的意思渣淤,盡管大多數(shù)JVM沒有這一代赏寇。
GC算法
? ? ? 常見的GC算法:復(fù)制、標(biāo)記-清除和標(biāo)記-壓縮
?一价认、 復(fù)制:
復(fù)制算法采用的方式為從根集合進(jìn)行掃描嗅定,將存活的對(duì)象移動(dòng)到一塊空閑的區(qū)域,如圖所示:
當(dāng)存活的對(duì)象較少時(shí)用踩,復(fù)制算法會(huì)比較高效(新生代的Eden區(qū)就是采用這種算法)渠退,其帶來的成本是需要一塊額外的空閑空間和對(duì)象的移動(dòng)。(缺點(diǎn):內(nèi)存使用率較低)
目前大部分垃圾收集器對(duì)于新生代都采取Copying算法脐彩,因?yàn)樾律忻看卫厥斩家厥沾蟛糠謱?duì)象碎乃,也就是說需要復(fù)制的操作次數(shù)較少,但是實(shí)際中并不是按照1:1的比例來劃分新生代的空間的丁屎,一般來說是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間荠锭,每次使用Eden空間和其中的一塊Survivor空間,當(dāng)進(jìn)行回收時(shí)晨川,將Eden和Survivor中還存活的對(duì)象復(fù)制到另一塊Survivor空間中证九,然后清理掉Eden和剛才使用過的Survivor空間。
對(duì)象的內(nèi)存分配共虑,往大方向上講就是在堆上分配愧怜,對(duì)象主要分配在新生代的Eden Space和From Space,少數(shù)情況下會(huì)直接分配在老年代妈拌。如果新生代的Eden Space和From Space的空間不足拥坛,則會(huì)發(fā)起一次GC蓬蝶,如果進(jìn)行了GC之后,Eden Space和From Space能夠容納該對(duì)象就放在Eden Space和From Space猜惋。在GC的過程中丸氛,會(huì)將Eden Space和From? Space中的存活對(duì)象移動(dòng)到To Space,然后將Eden Space和From Space進(jìn)行清理著摔。如果在清理的過程中缓窜,To Space無法足夠來存儲(chǔ)某個(gè)對(duì)象,就會(huì)將該對(duì)象移動(dòng)到老年代中谍咆。在進(jìn)行了GC之后禾锤,使用的便是Eden space和To Space了,下次GC時(shí)會(huì)將存活對(duì)象復(fù)制到From Space摹察,如此反復(fù)循環(huán)恩掷。當(dāng)對(duì)象在Survivor區(qū)躲過一次GC的話,其對(duì)象年齡便會(huì)加1供嚎,默認(rèn)情況下黄娘,如果對(duì)象年齡達(dá)到15歲,就會(huì)移動(dòng)到老年代中查坪。
一般來說寸宏,大對(duì)象會(huì)被直接分配到老年代,所謂的大對(duì)象是指需要大量連續(xù)存儲(chǔ)空間的對(duì)象偿曙,最常見的一種大對(duì)象就是大數(shù)組,比如:
byte[] data = new byte[4*1024*1024]
這種一般會(huì)直接在老年代分配存儲(chǔ)空間羔巢。
上一點(diǎn)講到MaxTenuringThreshold參數(shù)望忆,但虛擬機(jī)并不是永遠(yuǎn)地要求對(duì)象的年齡必須達(dá)到MaxTenuringThreshold才能晉升老年代,如果Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半竿秆,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代启摄,無須等到MaxTenuringThreshold中要求的年齡。
PS:如果晉升到老年代幽钢,但是老年代空間不足歉备,則會(huì)出發(fā)Full-GC
二、標(biāo)記-清除:
該算法采用的方式是從跟集合開始掃描匪燕,對(duì)存活的對(duì)象進(jìn)行標(biāo)記蕾羊,標(biāo)記完畢后,再掃描整個(gè)空間中未被標(biāo)記的對(duì)象帽驯,并進(jìn)行清除龟再。標(biāo)記和清除的過程如下:
上圖中藍(lán)色部分是有被引用的對(duì)象,褐色部分是沒有被引用的對(duì)象尼变。在Marking階段利凑,需要進(jìn)行全盤掃描,這個(gè)過程是比較耗時(shí)的。
清除階段清理的是沒有被引用的對(duì)象哀澈,存活的對(duì)象被保留牌借。
標(biāo)記-清除動(dòng)作不需要移動(dòng)對(duì)象,且僅對(duì)不存活的對(duì)象進(jìn)行清理割按,在空間中存活對(duì)象較多的時(shí)候走哺,效率較高,但由于只是清除哲虾,沒有重新整理丙躏,因此會(huì)造成內(nèi)存碎片。
CMS (java1.7以前主流)
優(yōu)點(diǎn):并發(fā)束凑、低停頓
缺點(diǎn):CPU敏感晒旅、浮動(dòng)垃圾、標(biāo)記-清除原地回收碎片多
G1(Garbage First) java1.9之后的默認(rèn)垃圾回收算法
優(yōu)點(diǎn):
G1是一個(gè)有整理內(nèi)存過程的垃圾收集器汪诉,不會(huì)產(chǎn)生很多內(nèi)存碎片废恋。
G1的Stop The World(STW)更可控,G1在停頓時(shí)間上添加了預(yù)測(cè)機(jī)制扒寄,用戶可以指定期望停頓時(shí)間鱼鼓。
1、邏輯分代该编,非物理分代迄本。
2、復(fù)制/清除(STW)優(yōu)先對(duì)可回收較大的區(qū)域進(jìn)行回收课竣。每次只清理一部分的嘉赎,增量式清理。由此來保證每次GC的STW(stop the world)不會(huì)停頓過長于樟。
3公条、可通過設(shè)置JVM的region(塊大小)1-32M、設(shè)置最大的期望GC停頓時(shí)間迂曲。靶橱。。
ZGC(java11過后路捧,64位系統(tǒng)关霸,支持4TB的堆)
二、標(biāo)記-整理(壓縮):
該算法與標(biāo)記-清除算法類似鬓长,都是先對(duì)存活的對(duì)象進(jìn)行標(biāo)記谒拴,但是在清除后會(huì)把活的對(duì)象向左端空閑空間移動(dòng),然后再更新其引用對(duì)象的指針涉波,如下圖所示
由于進(jìn)行了移動(dòng)規(guī)整動(dòng)作英上,該算法避免了標(biāo)記-清除的碎片問題炭序,但由于需要進(jìn)行移動(dòng),因此成本也增加了苍日。(該算法適用于舊生代)