Android 面試系統(tǒng)復(fù)習(xí)系列(三)Java 虛擬機(jī)原理
運(yùn)行時(shí)數(shù)據(jù)區(qū)
程序計(jì)數(shù)器(線程私有)
它可以看做當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。
虛擬機(jī)棧(線程私有)
虛擬機(jī)棧描述的是 Java 方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)方法從調(diào)用到執(zhí)行完成的過程,就對(duì)應(yīng)著一個(gè)棧幀再虛擬機(jī)棧中入棧到出棧的過程。
本地方法棧(線程私有)
同虛擬機(jī)棧,區(qū)別就是虛擬機(jī)棧執(zhí)行的是 Java 方法,本地方法棧執(zhí)行的是 Native 的方法。
Java 堆(線程共享)
存放對(duì)象實(shí)例,幾乎所有對(duì)象實(shí)例都在這里分配內(nèi)存,也是垃圾收集器管理的主要區(qū)域。
方法區(qū)(線程共享)
存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、及時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
垃圾回收機(jī)制
Java 虛擬機(jī)如何判斷一個(gè)對(duì)象可以回收
有兩種主流算法,一種是引用計(jì)數(shù)法,給對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器加一,引用失效時(shí)計(jì)數(shù)器減一。
這種算法簡單高效,但是卻避免不了互相循環(huán)引用無法回收的問題,所以 Java 虛擬機(jī)并沒有采用這種算法。
所以我們 Java 虛擬機(jī)采用的是第二種算法,可達(dá)性分析算法。
這個(gè)算法的基本思路就是通過一系列的成為“GC Roots”的對(duì)象作為七點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索走過的路徑稱為引用鏈,當(dāng)一個(gè)對(duì)象到 GC Roots沒有任何引用鏈相連時(shí),則證明此對(duì)象是不可達(dá)的,所以會(huì)被虛擬機(jī)判定為可回收的對(duì)象。
可作為 GC Roots 的對(duì)象
- 虛擬機(jī)棧中引用的對(duì)象
- 方法去中類靜態(tài)屬性引用的對(duì)象
- 方法區(qū)中常量引用的對(duì)象
- 本地方法棧中 JNI 引用的對(duì)象
垃圾回收算法
標(biāo)記-清除算法
最基礎(chǔ)的垃圾回收算法,先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。
優(yōu)點(diǎn):實(shí)現(xiàn)簡單
缺點(diǎn):一、效率低,標(biāo)記和清除兩個(gè)過程效率都不高。二、回收后可能產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會(huì)導(dǎo)致以后在分配大內(nèi)存對(duì)象時(shí),無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集。
復(fù)制算法
將內(nèi)存按容量劃分為大小相同的兩塊,然后每次只使用其中的一塊。當(dāng)一塊內(nèi)容用完了,就將還存活的對(duì)象移動(dòng)到另一塊,然后再將之前使用的內(nèi)存空間一次性清理掉。
優(yōu)點(diǎn):實(shí)現(xiàn)簡單,運(yùn)行高效
缺點(diǎn):會(huì)將可用內(nèi)存縮小為原來的一半,對(duì)象存活率高的話,效率會(huì)變低
現(xiàn)在商業(yè)虛擬機(jī)在回收新生代區(qū)域的時(shí)候都會(huì)采用這種算法,不過由于新生代里的對(duì)象絕大部分都是“朝生夕死”,所以并不需要按照 1:1 的比例來劃分內(nèi)存空間,HotSpot 虛擬機(jī)默認(rèn)比例是 8:1,所以空閑出來的內(nèi)存空間很小。不過在存活對(duì)象大于剩余的空間時(shí),會(huì)向老年代進(jìn)行分配擔(dān)保,直接進(jìn)入老年代。
標(biāo)記-整理算法
標(biāo)記的過程同標(biāo)記-清除算法的標(biāo)記過程,不過后續(xù)不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活對(duì)象都向一端移動(dòng),然后再清理邊界外的內(nèi)存。
老年代因?yàn)閷?duì)象存活率高,沒有額外空間對(duì)它分配擔(dān)保,所以一般使用標(biāo)記-清理或者標(biāo)記-整理算法進(jìn)行回收。
分代回收算法
當(dāng)前商業(yè)虛擬機(jī)的路基手機(jī)都采用這種算法,并沒有什么新的思想,只是根據(jù)對(duì)象的存活周期的不同,將內(nèi)存分為幾塊,一般是新生代,老年代,然后再根據(jù)其區(qū)域的特點(diǎn),采用不同的垃圾收集算法進(jìn)行回收。
Dalvik 虛擬機(jī)和 JVM 的區(qū)別
掘金上這篇文章寫得很清晰 https://juejin.im/post/59b7fa8cf265da066d3323bb
這里摘幾點(diǎn)主要的記一下:
- Dalvik 基于寄存器,基于寄存器的虛擬機(jī)雖然比基于堆棧的虛擬機(jī)在硬件,通用性上要差一些,但是它的代碼執(zhí)行效率去更好
- JVM 基于棧
- Dalvik虛擬機(jī)運(yùn)行的是其專有的文件格式Dex
- JVM 運(yùn)行 java 字節(jié)碼
ART 虛擬機(jī)
ART 的機(jī)制與 Dalvik 不同。在Dalvik下,應(yīng)用每次運(yùn)行的時(shí)候,字節(jié)碼都需要通過即時(shí)編譯器(just in time ,JIT)轉(zhuǎn)換為機(jī)器碼,這會(huì)拖慢應(yīng)用的運(yùn)行效率,而在ART 環(huán)境中,應(yīng)用在第一次安裝的時(shí)候,字節(jié)碼就會(huì)預(yù)先編譯成機(jī)器碼,使其成為真正的本地應(yīng)用。這個(gè)過程叫做預(yù)編譯(AOT,Ahead-Of-Time)。這樣的話,應(yīng)用的啟動(dòng)(首次)和執(zhí)行都會(huì)變得更加快速。
缺點(diǎn):
1.機(jī)器碼占用的存儲(chǔ)空間更大,字節(jié)碼變?yōu)闄C(jī)器碼之后,可能會(huì)增加10%-20%(不過在應(yīng)用包中,可執(zhí)行的代碼常常只是一部分。比如最新的 Google+ APK 是 28.3 MB,但是代碼只有 6.9 MB。)
2.應(yīng)用的安裝時(shí)間會(huì)變長。
每個(gè)小標(biāo)題都可作為面試題,故本篇沒有精心挑選的面試題