jvm組成
借用Java虛擬機(JVM)面試題的圖來看jvm
jvm由兩個子系統(tǒng)兩個組件組成爽待。兩個子系統(tǒng)分別是類加載子系統(tǒng)损同,執(zhí)行引擎。兩個組件是運行時數(shù)據(jù)區(qū)域鸟款,本地接口膏燃。
類加載子系統(tǒng):根據(jù)給定的類名,加載進運行時數(shù)據(jù)區(qū)域的方法區(qū)中
執(zhí)行引擎:執(zhí)行classes的指令
本地接口:與其他編程語言交互
運行時數(shù)據(jù)區(qū)域:jvm的內(nèi)存
作用:編譯器把java代碼變成字節(jié)碼何什,類加載器去加載字節(jié)碼到內(nèi)存中组哩,即到運行時數(shù)據(jù)區(qū)域的方法區(qū)中,然后字節(jié)碼通過執(zhí)行引擎變成底層的指令集处渣,指令集交給cpu的過程中要調(diào)用到本地接口來實現(xiàn)整個程序的功能伶贰。
jvm內(nèi)存區(qū)域
java虛擬機再執(zhí)行java程序的時候會把他管理的內(nèi)存劃分成不同的區(qū)域,有線程私有的罐栈,有線程共享的黍衙。這里借用公眾號JavaGuide的圖片
可以看到虛擬機棧,本地方法棧荠诬,程序計數(shù)器是線程私有的琅翻。堆,方法區(qū)柑贞,直接內(nèi)存是線程共享望迎。下面一個個介紹
程序計數(shù)器
程序計數(shù)器占一塊較小內(nèi)存,字節(jié)碼解釋器工作時通過改變程序計數(shù)器的值來選取下一條要執(zhí)行的字節(jié)碼指令凌外,同時每個線程有自己的程序計數(shù)器辩尊,意味著線程之間互不影響,切換線程后可以恢復(fù)到上次運行的位置康辑。
程序計數(shù)器是唯一一個不會出現(xiàn)OOM的內(nèi)存區(qū)域摄欲,因為他的生命周期隨著線程創(chuàng)建而創(chuàng)建,結(jié)束而死亡疮薇。
虛擬機棧
虛擬機棧是線程私有的胸墙,生命周期和線程相同。描述的java方法執(zhí)行的內(nèi)存模型按咒,每次方法調(diào)用的數(shù)據(jù)都是通過棧來傳遞迟隅。
java內(nèi)存大概分為棧內(nèi)存和堆內(nèi)存,棧指的是虛擬機棧中的局部變量表励七,主要存放編譯器可知的各種數(shù)據(jù)類型以及對象引用智袭。
java虛擬機棧會出現(xiàn)兩種錯誤StackOverFlowError與OutOfMemoryError。
本地方法棧
基本與虛擬機棧類似掠抬,虛擬機棧為虛擬機執(zhí)行java方法吼野,本地方法棧為虛擬機使用的native方法服務(wù)。
堆
堆是虛擬機中占內(nèi)存最大的一塊两波,負責(zé)存放對象實例瞳步,幾乎所有對象實例和數(shù)組都在這分配內(nèi)存闷哆。如果某些方法中的對象引用沒有被返回或者未被外面使用,就會分配內(nèi)存在棧上单起。
java堆是垃圾收集器管理的主要區(qū)域抱怔,由于現(xiàn)在都采用分代垃圾收集算法,所以堆細分成新生代嘀倒,老年代野蝇。
可以看到j(luò)vm分為堆內(nèi)存和非堆內(nèi)存,非堆內(nèi)存就是永久代括儒,又稱方法區(qū)绕沈。堆內(nèi)存存放的是對象,同時垃圾收集器就是判斷處理這些對象的帮寻。非堆內(nèi)存存放的是永久代乍狐,放的是程序運行時長期存在的對象,如類的方法固逗,常量浅蚪。
JDK8的時候廢除了永久代,然后在直接內(nèi)存里面弄了個元空間烫罩,都是方法區(qū)的實現(xiàn)惜傲。
方法區(qū)
方法區(qū)是各線程共享的一個區(qū)域,存儲類的常量贝攒,靜態(tài)變量等盗誊。方法區(qū)與永久代的關(guān)系引用文獻
《Java 虛擬機規(guī)范》只是規(guī)定了有?法區(qū)這么個概念和它的作?,并沒有規(guī)定如何去實現(xiàn)它隘弊。那么哈踱,在不同的 JVM 上?法區(qū)的實現(xiàn)肯定是不同的了。 ?法區(qū)和永久代的關(guān)系很像Java 中接?和類的關(guān)系梨熙,類實現(xiàn)了接?开镣,?永久代就是 HotSpot 虛擬機對虛擬機規(guī)范中?法區(qū)的?種實現(xiàn)?式。 也就是說咽扇,永久代是 HotSpot 的概念邪财,?法區(qū)是 Java 虛擬機規(guī)范中的定義,是?種規(guī)范质欲,?永久代是?種實現(xiàn)树埠,?個是標準?個是實現(xiàn),其他的虛擬機實現(xiàn)并沒有永久代這?說法把敞。
為什么要用元空間來代替永久代弥奸,永久代有jvm設(shè)置的固定大小榨惠,不能調(diào)整奋早,所以經(jīng)常發(fā)生空間溢出的錯誤盛霎,而元空間用的是直接內(nèi)存,就是你機器可用內(nèi)存的限制耽装,出現(xiàn)錯誤的概率比較小愤炸。
運行時常量池
運行時常量池在方法區(qū)里面,一開始運行時常量池邏輯包括字符串常量池在永久代里面掉奄,jdk7后就把字符串常量池移到了堆中规个,運行池常量池則還在方法區(qū),不過方法區(qū)從永久代變成了元空間姓建。
堆和棧的區(qū)別
- 棧是線程之間私有的诞仓,堆是所有線程共享的。
- 棧的存取速度比堆快速兔,
java類加載的過程
分為三個步驟墅拭,加載,連接涣狗,初始化谍婉。其中連接可以分為驗證,準備镀钓,解析穗熬。
加載:將class文件加載進內(nèi)存,并創(chuàng)建一個class對象丁溅。類的加載有類加載器來完成唤蔗。
驗證:確保加載類的信息符合規(guī)范,無安全問題窟赏。
準備:為類的靜態(tài)Field分配內(nèi)存措译,設(shè)置初始值(不是代碼設(shè)置的初始值,是java虛擬機的默認初始值)
解析:將類的二進制數(shù)據(jù)中的符號引用替換成直接引用
初始化:對類的變量初始化饰序,對static修飾的變量或者代碼塊進行初始化领虹。
類加載器有 啟動類加載器,擴展類加載器求豫,系統(tǒng)類加載器塌衰,用戶自定義類加載器
類加載的機制
雙親委托機制:首先,每個加載器都有對應(yīng)的父加載器蝠嘉,除了啟動類加載器最疆。
類加載器收到加載的請求,不會自己立馬加載蚤告,而是去把請求轉(zhuǎn)給父類努酸,如果父類還有父類,就繼續(xù)轉(zhuǎn)杜恰。當(dāng)轉(zhuǎn)到啟動類加載器(即沒有父類了)获诈,判斷啟動類加載器有沒有加載過仍源,有就加載成功,不能就回退給啟動類加載器的子類舔涎,嘗試是否被加載過笼踩,不能就繼續(xù)回退,一直到第一個類加載器自己加載為止亡嫌。
優(yōu)點:防止重復(fù)加載一個類嚎于,保證數(shù)據(jù)安全。
java對象的創(chuàng)建過程
分為類加載檢查挟冠,分配內(nèi)存于购,把內(nèi)存區(qū)域初始化零值,設(shè)置對象頭知染,執(zhí)行init方法价涝。
類加載檢查:檢查new后面的參數(shù)是否能在常量池中定位到這個類的符號引用,檢查是否被加載過持舆,沒有就按步驟去加載類色瘩。
分配內(nèi)存:為新的對象分配內(nèi)存,內(nèi)存大小在第一步就已經(jīng)知道了逸寓。分配方式有“指針碰撞”和“空閑列表”兩種居兆。
初始化零值:虛擬機將分配到的內(nèi)存都初始化為零值。(注意:不包括對象頭)
設(shè)置對象頭:把一些必要信息存放到對象頭中竹伸。
執(zhí)行init方法:執(zhí)行完new之后泥栖,已經(jīng)生成了一個可用的對象了,然后要按照程序員的意愿執(zhí)行init方法勋篓,就是把它設(shè)置成任意值這種吧享,人為的初始化。
對象的訪問方式
訪問方式由虛擬機來實現(xiàn)譬嚣,一般由兩種钢颂,句柄訪問,直接指針拜银。
內(nèi)存分配
堆內(nèi)存分為新生代殊鞭,老年代。新生代又分為eden區(qū)尼桶,survivor from(s0)操灿,survivor to(s1),eden區(qū)域最大泵督。一般對象會在eden區(qū)分配趾盐,當(dāng)eden沒有足夠空間去分配,就發(fā)起一次Minor GC(新生代垃圾收集)。大對象(需要大量連續(xù)內(nèi)存空間)直接進入老年代救鲤,長期存活的對象進入老年代久窟。每個對象都有一個age計數(shù)器,對象在survivor區(qū)每經(jīng)過一次Minor GC就增加一歲蜒简,如果到了默認值(一般15)瘸羡,就會變成老年代漩仙。
判斷對象死亡的兩種方法
堆進行回收要判斷對象是否已經(jīng)死亡(不再被任何途徑使用的對象)搓茬。
引用計數(shù)法:每個對象添加一個引用計數(shù)器,有被引用就+1队他,失效就-1卷仑。如果計數(shù)器為0就是不可能再被使用的了。
可達性分析算法:把名字是GC Roots的對象作為起點麸折,通過這些起點開始往下搜索锡凝,節(jié)點走過的路徑就是引用鏈,如果一個對象到GC Roots沒有任何引用鏈垢啼,就代表對象不可用了窜锯,可以被回收了
圖片出自JavaGuide的復(fù)習(xí)資料
垃圾收集算法
- 標記-清理算法
先標記出所有不需要回收的對象,然后把沒有標記的對象都給回收了芭析。有效率問題和回收后存在大量不連續(xù)的碎片問題锚扎。 - 復(fù)制算法
先把內(nèi)存分為兩塊相同大小的內(nèi)存塊,每次使用都只在一個上面使用馁启,當(dāng)這塊內(nèi)存使用完后驾孔,回收掉不需要的對象,然后把剩下的對象復(fù)制到另一個內(nèi)存上面惯疙,再把這個內(nèi)存塊全部清理掉翠勉,下次再使用。 - 標記-整理算法
先進行標記不需要回收的對象霉颠,然后把所有對象移動到另一端对碌,然后直接清理掉邊界以外的內(nèi)存。 - 分代收集算法
根據(jù)存活周期分為不同代的對象蒿偎,如新生代俭缓,老年代一樣。根據(jù)每個年代的特點執(zhí)行相對應(yīng)的算法酥郭。如新生代使用復(fù)制算法华坦,老年代使用標記清理,標記-整理算法不从。這樣子就可以提高gc的效率惜姐。
四個引用
引用計數(shù)法和可達性分析算法都是判斷對象是否被引用。引用又可以分為強引用,軟引用歹袁,弱引用坷衍,虛引用。
強引用:大部分引用都是強引用条舔,垃圾回收器決定不會回收強引用沛膳,即使拋出OOM錯誤线梗,終止程序都不會隨意回收強引用對象。可能會導(dǎo)致內(nèi)存泄露糠排。
在安卓中一般就是new一個對象就是強引用了哗伯。
軟引用:軟引用如果內(nèi)存足夠就不管他苇瓣,如果內(nèi)存不足了匠楚,就會開始回收他。只要沒被回收就可以使用摊沉。
從網(wǎng)絡(luò)上獲取照片并顯示時狐史,用軟引用緩存下來。下次再去網(wǎng)絡(luò)上獲取的時候就可以先判斷該圖片有沒有被緩存说墨,有的話就顯示骏全。
弱引用:如果垃圾回收器發(fā)現(xiàn)了弱引用的對象,不管內(nèi)存是否充足尼斧,都會進行回收姜贡。但是垃圾回收機制優(yōu)先級低,所以一般不會立馬回收掉突颊。
安卓中也可以緩存一些數(shù)據(jù)鲁豪,防止內(nèi)存泄露。
虛引用:如果一個對象有虛引用律秃,和沒有引用一樣爬橡。任何時候都可能被回收。虛引用主要用于跟蹤對象被垃圾回收的活動棒动。