網(wǎng)上有很多關(guān)于pos機(jī)面試技巧,聊聊 GC 機(jī)制的知識,也有很多人為大家解答關(guān)于pos機(jī)面試技巧的問題,今天pos機(jī)之家(www.afbey.com)為大家整理了關(guān)于這方面的知識,讓我們一起來看下吧!
本文目錄一覽:
pos機(jī)面試技巧
前言
GC 中文直譯垃圾回收,是一種回收內(nèi)存空間避免內(nèi)存泄漏的機(jī)制。當(dāng) JVM 內(nèi)存緊張,通過執(zhí)行 GC 有效回收內(nèi)存,轉(zhuǎn)而分配給新對象從而實(shí)現(xiàn)內(nèi)存的再利用。 JVM GC 機(jī)制雖然無需開發(fā)主動參與,減輕不少工作量,但是某些情況下,自動 GC 將會導(dǎo)致系統(tǒng)性能下降,響應(yīng)變慢,所以這就需要我們提前了解掌握 GC 機(jī)制。當(dāng)面對這種情況時,才能從容不迫的解決問題。另外 GC 機(jī)制也是 Java 面試高頻考題,了解掌握 GC 是一項(xiàng)必備技能。
學(xué)習(xí) GC ,首先我們解決三個問題:
什么是垃圾在哪里回收垃圾怎么回收垃圾什么是垃圾我們先來看一段簡單的代碼。
上面代碼通過將字符串對象轉(zhuǎn)化成字節(jié)數(shù)組,然后寫入本地文件。方法一旦開始執(zhí)行,就將會在分配一定內(nèi)存給新建的對象,然后將引用告訴了str, bytes 變量。等到方法執(zhí)行完畢,方法內(nèi)部局部變量緊接將就會被銷毀。但是這樣僅僅銷毀了局部變量,卻沒有帶走內(nèi)存上這些實(shí)際的對象。這類不再起作用,沒有被引用的對象,將其歸類為垃圾。
在偌大的內(nèi)存上存活著無數(shù)對象,GC 之前需要準(zhǔn)確將這些對象標(biāo)記出來,分為存活對象與垃圾對象。這個過程一旦少標(biāo)記,那就只能等待下次 GC標(biāo)記,再回收,這樣將會影響 GC 效率。另外決不能錯標(biāo)記,將正常存活對象標(biāo)記為垃圾。一旦回收正常存活的對象,可能就會引起程序各種崩潰。
目前有兩種算法可以用來標(biāo)記:
引用計(jì)數(shù)法可達(dá)性分析法引用計(jì)數(shù)法引用計(jì)數(shù)法通過在對象頭分配一個字段,用來存儲該對象引用計(jì)數(shù)。一旦該對象被其他對象引用,計(jì)數(shù)加 1。如果這個引用失效,計(jì)數(shù)減 1。當(dāng)引用計(jì)數(shù)值為 0 時,代表這個對象已不再被引用,可以被回收。
如上圖所示,當(dāng) str 引用堆中對象時,計(jì)數(shù)值增加為 1。當(dāng) str 變?yōu)?null 時,既不再引用該對象,計(jì)數(shù)值減 1。此時該對象就可以被 GC 回收。
引用計(jì)數(shù)法只需要判斷計(jì)數(shù)值,所以實(shí)現(xiàn)比較簡單,這個過程也比較高效。但是存在一個很嚴(yán)重的問題,無法解決對象循環(huán)引用問題。
從上圖可以看到, a,b 不再引用堆中對象,導(dǎo)致計(jì)數(shù)減一。此時兩個對象內(nèi)部還存在互相引用,計(jì)數(shù)值不為 0,此時 GC 沒辦法回收該對象。
可達(dá)性分析法這個算法首先需要按照規(guī)則查找當(dāng)前活躍的引用,將其稱為 GC Roots。接著將 GC Roots 作為根節(jié)點(diǎn)出發(fā),遍歷對象引用關(guān)系圖,將可以遍歷(可達(dá))的對象標(biāo)記為存活,其余對象當(dāng)做無用對象。
注意這里是是引用,而不是對象。
從上圖可以看到,綠色對象雖然存在循環(huán)引用,但是由于這些對象不能被 GC Roots 遍歷到,所以將會被回收。
可以被當(dāng)做GC Roots 活躍引用包括但不限于以下引用:
方法中局部變量靜態(tài)變量,常量JNI handles….在哪里回收垃圾還記得剛開始接觸 Java 時,只知道堆棧,對象實(shí)例分配在堆中,方法中局部變量位于棧中。實(shí)際上 JVM 內(nèi)存區(qū)域劃分更加細(xì)致,分為:
堆方法區(qū)虛擬機(jī)棧本地方法棧程序計(jì)數(shù)器如圖所示,我們將內(nèi)存劃分為線程私有與線程共享的區(qū)域。方法區(qū)與堆都是線程共享的區(qū)域,這兩部分占用 JVM 大部分內(nèi)存,剩下三個小弟將會跟線程綁定,隨著線程消亡,自動將會被 JVM 回收。
堆
堆應(yīng)該是大家最熟悉的一塊區(qū)域,幾乎所有對象實(shí)例都將會在此出生,通常也是虛擬機(jī)上占用內(nèi)存最大一塊區(qū)域,簡直就是 JVM 內(nèi)存中的大哥大。堆內(nèi)存內(nèi)部也不是簡簡單單一塊而已,目前將會根據(jù)分代算法,將堆分代,不同對象位于不同區(qū)域。這一點(diǎn)我們下文再詳細(xì)了解。
方法區(qū)
方法區(qū)將會保存已被虛擬上加載的類信息、常量,靜態(tài)變量,字節(jié)碼等信息,堆上的對象正式通過方法區(qū)這些信息,才能正確創(chuàng)建出來。
棧
虛擬機(jī)棧棧由一系列棧幀組成,每個棧幀其實(shí)代表一個方法,棧幀中將會保存一個方法的局部變量表,方法出入口信息,操作棧等。每當(dāng)調(diào)用一個方法,就將會把這個棧幀壓入棧中,執(zhí)行結(jié)束,出棧。
本地方法棧與虛擬機(jī)棧比較類似,最大區(qū)別在于,虛擬機(jī)棧執(zhí)行的 Java 方法,而本地方法棧將會用來執(zhí)行 Native 方法服務(wù)。下面方法就會在本地方法棧中執(zhí)行。
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
程序計(jì)數(shù)器
程序計(jì)算器可以說是這幾塊區(qū)域占用最小的一部分,但是功能卻十分重要。Java 源代碼通過編譯變成字節(jié)碼,然后被 JVM 載入運(yùn)行之后,將會變成一條條指令,而程序計(jì)數(shù)器的工作就是告訴當(dāng)前線程下一條需要執(zhí)行指令。這樣即使發(fā)生了線程切換,等待恢復(fù)的時候,當(dāng)前線程依然知道接下去要執(zhí)行的指令。
怎么回收目前主流 GC 算法主要分為三種:
標(biāo)記-清除算法復(fù)制算法標(biāo)記-整理算法標(biāo)記-清除算法這是一個最為基礎(chǔ)也是最容易實(shí)現(xiàn)的算法,主要實(shí)現(xiàn)步驟分為兩步:標(biāo)記,清除。
標(biāo)記:通過上述 GC Roots 標(biāo)記出可達(dá)對象。清除:清理未標(biāo)記對象。ps:這個圖著實(shí)難畫啊。。。。
可以看到經(jīng)過這個算法回收之后,雖然堆空間被清理出來,但是也產(chǎn)生很多空間碎片。這就會導(dǎo)致一個新對象根據(jù)堆剩余容量計(jì)算,看起來是可以分配,但是實(shí)際分配過程,由于沒有連續(xù)內(nèi)存,導(dǎo)致虛擬機(jī)感知到內(nèi)存不足,又不得不提前再次觸發(fā) GC。
可能這里你就會有疑惑,為什么對象需要分配一塊連續(xù)的內(nèi)存?
這里引用一下 R 神 @RednaxelaFX 答案。
另外這個算法還有一個不足:標(biāo)記與清除效率比較低。這就竟會導(dǎo)致 GC 占用時間過長,影響正常程序使用。
復(fù)制算法為了解決上述效率問題,誕生復(fù)制算法。這個算法將可用內(nèi)存分為兩塊,每次只使用其中一塊,當(dāng)這一塊內(nèi)存使用完畢,觸發(fā) GC ,將會把存活的對象依次復(fù)制到另外一塊上,然后再把已使用過的內(nèi)存一次性清理。
這個算法每次只需要操作一半內(nèi)存,GC 回收之后也不存在任何空間碎片,新對象內(nèi)存分配時只需要移動堆頂指針,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡單,運(yùn)行高效。但是這個算法閑置一半內(nèi)存空間,空間利用效率不高。
PS:復(fù)制算法以空間換時間,兩者不可兼得
另外對象存活率也會影響復(fù)制算法效率。如果對象大部分都是朝生夕死,只需要移動少量存活對象,就能騰出大部分空間。反而如果對象存活率高,這就需要進(jìn)行較多的復(fù)制操作,回收之后也并沒有多余內(nèi)存,這就可能導(dǎo)致頻繁觸發(fā) GC。
針對這種存活時間長的對象,就需要使用標(biāo)記-整理算法。
標(biāo)記-整理算法標(biāo)記-整理算法可以說是標(biāo)記-清除算法的改進(jìn)版,改進(jìn)了清除導(dǎo)致的空間碎片問題。這個算法分為兩步:
標(biāo)記:也是通過 GC Roots 標(biāo)記存活對象。整理:將存活對象往一端移動,按照內(nèi)存地址一次排序,然后將末端邊界之外內(nèi)存直接清理。雖然標(biāo)記-整理算法解決了標(biāo)記-清除算法空間碎片問題,也完整利用整個內(nèi)存空間,但是這個算法問題效率并不高。相較于標(biāo)記-清除算法,標(biāo)記-整理算法多增加整理這一步,所以該算法效率還低于標(biāo)記-清除算法。
分代收集算法從上面三種 GC 算法可以看到,并沒有一種空間與時間效率都是比較完美的算法,所以只能做的是綜合利用各種算法特點(diǎn)將其作用到不用的內(nèi)存區(qū)域。
目前商業(yè)虛擬機(jī)根據(jù)對象存活周期不同劃分內(nèi)存區(qū)域,一般分為新生代,老年代。新對象一般情況都會優(yōu)先分配在新生代,新生代對象若存活時間大于一定閾值之后,將會移到至老年代。新生代的對象都是短命鬼,老年代的對象都是長壽先生。
新生代每次 GC 之后都可以回收大批量對象,所以比較適合復(fù)制算法,只需要付出少量復(fù)制存活對象的成本。這里內(nèi)存劃分并沒有按照 1:1 劃分,默認(rèn)將會按照 8:1:1 劃分成 Eden 與兩塊 Survivor空間。每次使用 Eden 與一塊Survivor空間,這樣我們只是閑置 10% 內(nèi)存空間。不過我們每次回收并不能保證存活對象小于 10%,在這種情況下就需要依靠老年代的內(nèi)存分配擔(dān)保。當(dāng)Survivor空間并不能保存剩余存活對象,就將這些對象通過分配擔(dān)保進(jìn)制移動至老年代。
老年代中對象存活率將會特別高,且沒有額外空間進(jìn)行分配擔(dān)保,所以并不適合復(fù)制算法,所以需要使用標(biāo)記-清除或標(biāo)記-整理算法。
隨便聊聊最近又到一年一次大考的時候,不得不又拿起周志明『深入 Java 虛擬機(jī)』重新學(xué)習(xí)。還記得第一次翻看這本書的時候,大半內(nèi)容看不懂,看完也很快就忘了。然后過了一段時間,又重新拿起此書,這次比上次好,也已經(jīng)能看小大半了。最近跟一些小伙伴聊天,發(fā)現(xiàn)他們都是看這本書學(xué)習(xí) JVM ,不得不說這本書真是一本神書。最近『深入 Java 虛擬機(jī)』第三版即將上架開售,有需要的小伙伴可以考慮入手了。
好了 ,GC 機(jī)制就就總結(jié)到這里,下一篇我們來聊聊 JVM 常用 GC 回收器。
幫助鏈接GC Roots Java虛擬機(jī)詳解04—-GC算法和種類 深入 Java 虛擬機(jī)
以上就是關(guān)于pos機(jī)面試技巧,聊聊 GC 機(jī)制的知識,后面我們會繼續(xù)為大家整理關(guān)于pos機(jī)面試技巧的知識,希望能夠幫助到大家!
