面試篇:虛擬機(jī)棧5連問(wèn)?一聽(tīng)心里就樂(lè)了
面試路上
“滴,滴滴......”師傅我們到哪了?我還要趕著面試呢。
「師傅:」 快了快了,下個(gè)路口就到了。真是服了這幫人了,不會(huì)開(kāi)車(chē)凈往里湊。
聽(tīng)著司機(jī)師傅的抱怨聲,不禁想起首打油詩(shī):滿目尾燈紅,耳盈刺笛聲。心憂遲到久,頹首似雷轟。
一下車(chē)趕緊小跑就進(jìn)了富麗堂皇的酒店,不不不,是商務(wù)樓,這大廳有點(diǎn)氣派,讓我有點(diǎn)想入非非呀。
面試經(jīng)過(guò)
“咚咚咚”,“請(qǐng)進(jìn)”。
「面試官:」 小伙子長(zhǎng)得挺帥呀,年輕人就是有活力,來(lái)先做個(gè)簡(jiǎn)單的自我介紹吧。
「阿Q:」 面試官你好,My name is “影流之主”,來(lái)自艾歐尼亞,是LOL中的最強(qiáng)中單(不接受反駁),論單殺沒(méi)有服過(guò)誰(shuí)。我的口頭禪是“無(wú)形之刃,最為致命”,當(dāng)然你也可以叫我阿Q,這是我的簡(jiǎn)歷。
「面試官:」 阿Q,那咱也不寒暄了,直接切正題吧。看你jvm寫(xiě)的知識(shí)點(diǎn)最多,那就先說(shuō)一下你對(duì)虛擬機(jī)棧的理解吧。
「阿Q:」 內(nèi)心OS:這波可以吹X了???..咳...虛擬機(jī)棧早期也叫java棧,是在jvm的運(yùn)行時(shí)數(shù)據(jù)區(qū)存在的一塊內(nèi)存區(qū)域。它是線程私有的,隨線程創(chuàng)建而創(chuàng)建,隨線程消亡而結(jié)束。
嗯。。。假裝想一下??
眾所周知,棧只有進(jìn)棧和出棧兩種操作,所以它是一種快速有效的分配存儲(chǔ)方式。對(duì)于它來(lái)說(shuō),它不存在垃圾回收問(wèn)題,但是它的大小是動(dòng)態(tài)的或者固定不變的,因此它會(huì)存在棧溢出或者內(nèi)存溢出問(wèn)題......
「面試官:」 打斷一下啊,你剛才說(shuō)會(huì)存在棧溢出和內(nèi)存溢出問(wèn)題,那你能分別說(shuō)一下為什么會(huì)出現(xiàn)這種情況嗎?
「阿Q:」 可以可以,我們知道虛擬機(jī)棧由棧幀組成,每一個(gè)方法的調(diào)用都對(duì)應(yīng)著一個(gè)棧幀的入棧。我們可以通過(guò)-Xss參數(shù)來(lái)設(shè)置棧的大小,假設(shè)我們?cè)O(shè)置的虛擬機(jī)棧大小很小,當(dāng)我們調(diào)用的方法過(guò)多,也就是棧幀過(guò)多的話,就會(huì)出現(xiàn)StackOverflowError,即棧溢出問(wèn)題。
假如我們的棧幀不固定,設(shè)置為動(dòng)態(tài)擴(kuò)展的,那在我們的內(nèi)存不足時(shí),也就沒(méi)有足夠的內(nèi)存來(lái)支持棧的擴(kuò)展,這個(gè)時(shí)候就會(huì)出現(xiàn)OOM異常,即內(nèi)存溢出問(wèn)題。
「面試官:」 嗯嗯(點(diǎn)頭狀),示意小伙子思路很清晰呀,那你剛才說(shuō)到棧幀設(shè)置的太小會(huì)導(dǎo)致棧幀溢出問(wèn)題,那我們?cè)O(shè)置的大點(diǎn)不就可以完全避免棧溢出了嘛。
「阿Q:」 一聽(tīng)就是要給我挖坑呀,像我們一般都比較崇尚中庸之道,所以一聽(tīng)到這種絕對(duì)的問(wèn)題,必須機(jī)靈點(diǎn):不不不,調(diào)整棧的大小只可以「延緩」棧溢出的時(shí)間或者說(shuō)減少棧溢出的風(fēng)險(xiǎn)。
舉個(gè)例子吧
假如一個(gè)業(yè)務(wù)邏輯的方法調(diào)用需要5000次,但是此時(shí)拋出了棧溢出的錯(cuò)誤。我們可以通過(guò)設(shè)置-Xss來(lái)獲取更大的??臻g,使得調(diào)用在7000次時(shí)才會(huì)溢出。此時(shí)調(diào)整棧大小就變得很有意義,因?yàn)檫@樣就會(huì)使得業(yè)務(wù)能正常支持。
那假如是有「死遞歸」的情況則無(wú)論怎么提高棧大小都會(huì)溢出,這樣也就沒(méi)有任何意義了。
「面試官:」 好的,那你看一下這個(gè)簡(jiǎn)單的小程序,你能大體說(shuō)一下它在內(nèi)存中的執(zhí)行過(guò)程嗎?
- public void test() {
- byte i = 15;
- int j = 8;
- int k = i + j;
- }
來(lái)張圖,便于大家更好地理解
「阿Q:」 先把該代碼編譯一下,然后查看它的字節(jié)碼文件。如上圖中左邊所示,執(zhí)行過(guò)程如下:
- 首先將要執(zhí)行的指令地址0存放到PC寄存器中,此時(shí),局部變量表和操作數(shù)棧的數(shù)據(jù)為空;
- 當(dāng)執(zhí)行第一條指令bipush時(shí),將操作數(shù)15放入操作數(shù)棧中,然后將PC寄存器的值置為下一條指令的執(zhí)行地址,即2;
- 當(dāng)執(zhí)行指令地址為2的操作指令時(shí),將操作數(shù)棧中的數(shù)據(jù)取出來(lái),存到局部變量表的1位置,因?yàn)樵摲椒ㄊ菍?shí)例方法,所以0位置存的是this的值,PC寄存器中的值變?yōu)?;
- 同步驟2和3將8先放入操作數(shù)棧,然后取出來(lái)存到局部變量表中,PC寄存器中的值也由3->5->6;
- 當(dāng)執(zhí)行到地址指令為6、7、8時(shí),將局部變量表中索引位置為1和2的數(shù)據(jù)重新加載到操作數(shù)棧中并進(jìn)行iadd加操作,將得到的結(jié)果值存到操作數(shù)棧中,PC寄存器中的值也由6->7->8->9;
- 執(zhí)行操作指令istore_3,將操作數(shù)棧中的數(shù)據(jù)取出存到局部變量表中索引為3的位置,執(zhí)行return指令,方法結(jié)束。
「面試官:」 內(nèi)心OS:這小子貌似還可以呀。說(shuō)的還不錯(cuò),那你能說(shuō)一下方法中定義的局部變量是否線程安全嗎?
「阿Q:」 那我再用幾個(gè)例子來(lái)說(shuō)一下吧。
- public class LocalParaSafeProblem {
- /**
- * 線程安全的
- * 雖然StringBuilder本身線程不安全,
- * 但s1 變量只存在于這個(gè)棧幀的局部變量表中,
- * 因?yàn)闂敲總€(gè)線程獨(dú)立的一份,
- * 所以這里的s1是線程安全的
- */
- public static void method01() {
- // 線程內(nèi)部創(chuàng)建的,屬于局部變量
- StringBuilder s1 = new StringBuilder();
- s1.append("a");
- s1.append("b");
- }
- /**
- * 線程不安全
- * 因?yàn)榇藭r(shí)StringBuilder是作為參數(shù)傳入,
- * 外部的其他線程也可以訪問(wèn),所以線程不安全
- */
- public static void method02(StringBuilder stringBuilder) {
- stringBuilder.append("a");
- stringBuilder.append("b");
- }
- /**
- * 線程不安全
- * 此時(shí)StringBuilder被多個(gè)線程同時(shí)操作
- */
- public static void method03() {
- StringBuilder stringBuilder = new StringBuilder();
- new Thread(() -> {
- stringBuilder.append("a");
- stringBuilder.append("b");
- }, "t1").start();
- method02(stringBuilder);
- }
- /**
- * 線程不安全
- * 因?yàn)榇藭r(shí)方法將StringBuilder返回出去了
- * 外面的其他線程可以直接修改StringBuilder這個(gè)引用了所以不安全
- */
- public static StringBuilder method04() {
- StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.append("a");
- stringBuilder.append("b");
- return stringBuilder;
- }
- /**
- * StringBuilder是線程安全的
- * 此時(shí)stringBuilder值在當(dāng)前棧幀的局部變量表中存在,
- * 其他線程無(wú)法訪問(wèn)到該引用,
- * 方法執(zhí)行完成之后此時(shí)局部變量表中的stringBuilder的就銷(xiāo)毀了
- * 返回的stringBuilder.toString()線程不安全
- * 最后的返回值將toString返回之后,其他線程可以操作而String本身是線程不安全的。
- */
- public static String method05() {
- StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.append("a");
- stringBuilder.append("b");
- return stringBuilder.toString();
- }
- }
看到這估計(jì)會(huì)有點(diǎn)繞,那我就總結(jié)一下吧:如果對(duì)象是在方法內(nèi)部產(chǎn)生且在內(nèi)部消亡,不會(huì)返回到外部就不存在線程安全問(wèn)題;反之如果類(lèi)本身線程不安全的話就存在線程安全問(wèn)題。
「面試官:」 不錯(cuò)不錯(cuò),有理有據(jù),那你再說(shuō)說(shuō)你對(duì)堆內(nèi)存的理解吧。
「阿Q:」 唉,今天太累了,說(shuō)了一天這個(gè)了,不想說(shuō)了。
「面試官:」 那好吧,那我們今天先到這吧,回去等通知吧。
本文轉(zhuǎn)載自微信公眾號(hào)「阿Q說(shuō)代碼」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系阿Q說(shuō)代碼公眾號(hào)。