1. 程式人生 > >【原創】面試官:你回去等通知吧!

【原創】面試官:你回去等通知吧!

這是why技術的第37篇原創文章

老規矩,先聊聊生活,上面這張圖片是我週一拍的。

週一晚上下班後發現公司樓下推著三輪車賣花的阿姨又開始買花了。整個路口只有她一個人在做生意,整條路上也沒有幾個人,大家都低著頭匆匆走著,繁花中帶著點憂傷。

於是,我去買了一把白玫瑰。

上週日把《霍亂時期的愛情》看完了,就剛好當道具拍了上面的照片。總體來說我不喜歡這種縱情聲色的故事,更不喜歡那個看起來冠冕堂皇的理由∶“我一生有622個情人,但是我只愛過你”。雖然它真的是窮極了愛情的所有可能性,但是它不夠真實。

相比之下我覺得錢鍾書先生寫的《圍城》∶“我說的讓她三分,不是三分流水七分塵的三分,而是天下明月只有三分的三分。”這樣打打鬧鬧的愛情更加真實。

再看楊絳先生的《我們仨》,書的最後她說∶“世間好物不堅牢,彩雲易散琉璃脆”。這才是愛情,這才是真實的生活。

好了,說迴文章。

對不起,我錯了。

前面發的這兩篇文章:

《面試官:你說你熟悉jvm?那你講一下併發的可達性分析》

《面試官:G1回收器怎麼知道你是什麼時候的垃圾?》

裡面有一些沒有說清楚的地方,又有很多讀者來問,所以我覺得需要補充說明一下。

更重要的是,經過高手指點,其中還有一些描述錯誤的地方,我也需要進行勘誤。

如果真的是面試題,可能面試官就會對我說:好了,我們今天就先到這裡。你回去等通知吧。

如果你沒看過我剛剛說的兩篇文章,我建議你不要看這篇,因為一看就得看三篇,如果裡面的衍生知識點你還想徹底弄明白,一個下午就過去了......(當然,你看了後收穫肯定還是有的。)

如果你看了我之前的兩篇文章,我求求你一定看看這篇,補充、更正一下答案,等面試官真的問起細節來,也不怕......

好了,在閱讀本文之前,我假設你已經讀過我前面說的兩篇優質、幽默、有料的文章了。

併發的可達性分析-勘誤

之前釋出了這篇文章《面試官:你說你熟悉jvm?那你講一下併發的可達性分析》,對於文中這一部分內容中的動圖,有很多朋友給我說看不懂:

我把這個動圖拿出來:

首先,需要說明的是,我現在也看不懂這個動圖了。(畫錯了就是畫錯了,還強行找個理由)。

接下來,忘記這個動圖,我們重新分析一波原始快照方案(以下簡稱SATB,Snapshot At The Beginning)。

首先,我們看初始標記階段(即根節點列舉)完成後,剛剛進入併發標記階段,GC 執行緒開始掃描時的物件圖:

在上面這張圖裡,當GC Roots確定後,物件圖就已經確定了。SATB掃描的時候基於已經確定的物件圖(快照版的物件圖)掃描,也就是說掃描過程中上面的快照圖的引用關係是不會發生變化的,但是真實的物件圖是會發生變化的。

舉個例子:就類似於你在操場上拍了一張照片,你數照片裡面的人數,照片是不會發生變化,人數一直都是這麼多,但是真實的操場上的人是在時刻變化的。

所以,在物件圖確定的一刻,正常掃描完成後,物件圖變成了下面這樣:

好了,面前的鋪墊完成了。

我們這裡需要演示的是“物件消失”情況。

首先,我們先確定一下上面展示的物件圖,在併發標記階段必然有一個時刻的物件圖是這樣的:

我們基於這個時刻的這個物件圖去討論“物件消失”的問題。

還得記得"物件消失"必須同時滿足的兩個條件嗎?(這兩個條件是摘抄自《深入理解Java虛擬機器(第3版)》P.89)

條件一:賦值器插入了一條或者多條從黑色物件到白色物件的新引用。

條件二:賦值器刪除了全部從灰色物件到該白色物件的直接或間接引用。

我們再仔細的讀一遍第二個條件,你會發現,它說的是**“該白色物件”。這個“該白色物件”指的是條件一里面的白色物件。**

所以,我們有理由相信:條件一和條件二是有先後順序的,即必須是賦值器插入了一條或者多條從黑色物件到白色物件的新引用,然後賦值器又刪除了全部從灰色物件到該白色物件的直接或間接引用。在這樣的情況下,才會出現“物件消失”的情況。

經過高人指點,我們還可以進行反證法,如下:

我們假設灰色物件到白色物件的引用先刪除了,即先觸發了條件二。那麼對應的這個時刻真實的物件圖將變成下面的樣子:

(注意我這裡強調的是真實的物件圖,而不是快照的物件圖。再次重申:快照的物件圖在掃描開始的時候就確定了,掃描過程中是不會變化的。)

那麼,白色物件9是處於遊離態的,從根節點沒有任何引用鏈相連,用圖論的話來說就是從 GC Root 到物件9不可達,則證明此物件是不可能再被使用的。因此使用者執行緒不可能把黑色物件5指向遊離態的白色物件9,你寫不出這樣的程式碼來。

如果說上面的圖你一眼沒看出來,那麼請看下面這圖,是不是恍然大悟:

黑色物件5不能指向白色物件9,那麼第一條規則就滿足不了了。

所以,綜上我們可以得出:條件一和條件二是有先後順序的。

那麼我們根據條件一繼續做圖如下:

條件一是賦值器插入了一條或者多條從黑色物件到白色物件的新引用。

在上面這個圖的場景中,就是 GC 執行緒在工作的同時,賦值器插入了一條黑色物件5到白色物件9之間的新引用。(用紅色線條以示區分)

在這個時刻,由於灰色物件6指向白色物件9,所以黑色物件5可以指向白色物件9,想一想我們前面的證明,只要有引用鏈,黑色物件就可以到達白色物件。

這個時候僅僅滿足了條件一,物件還沒消失。

接下來就是條件二的圖,STAB破壞的就是條件二:

條件二是賦值器刪除了全部從灰色物件到該白色物件的直接或間接引用。

在上面這個圖的場景中,就是賦值器刪除了灰色物件6到白色物件9的直接引用。

這個時候白色物件9就是“消失的物件”了,因為黑色的物件5是不會被再次掃描的。

需要注意的是,賦值器可以理解為使用者執行緒,由於在併發標記階段,使用者執行緒和 GC 執行緒在同時執行,所以需要出現上面的圖,還有一個前置條件就是:

使用者執行緒刪除物件6到物件9之間的引用,要先於 GC 執行緒掃描到物件6,把物件6變成灰色的操作。因為只有這樣,GC 執行緒處理到物件6的時候,才有對應的寫屏障記錄。

如果在 GC 執行緒已經掃描過物件6,即物件6已經是黑色的情況下(這個時候物件9,不是黑色就是灰色,不可能是白色),使用者執行緒再去刪除物件6到物件9之間的引用,GC 執行緒是不需要處理的,因為物件9已經是非白了,它在本輪中必定會活下來。

這裡我引用R大的描述:

https://hllvm-group.iteye.com/group/topic/44381?page=2

因為刪除操作會觸發 pre-write barrier,把每次引用關係變化時舊的引用值記下來,只有這樣,等 GC 執行緒到達某一個物件時,這個物件的所有引用型別欄位的變化全都有記錄在案,就不會漏掉任何在快照圖裡活的物件。當然,很可能有物件在快照中是活的,但隨著併發 GC 的進行它可能本來已經死了,但 SATB 還是會讓它活過這次 GC,變成了浮動垃圾。

SATB 在寫屏障裡,把舊的引用所指向的物件都變成非白的(已經黑灰就不用管,還是白的就變成灰的)。

這樣做的實際效果是:如果一個灰物件的欄位原本指向一個白物件,但在concurrent marker能掃描到這個欄位之前,這個欄位被賦上了別的值(例如說null),那麼這個欄位跟白物件之間的關聯就被切斷了。SATB write barrier保證在這種切斷髮生之前就把欄位原本引用的物件變灰,從而杜絕了上述條件二的發生。

其中:“把舊的引用所指向的物件都變成非白的。”在我們這個場景下含義如下:

舊的引用指的是:灰色物件6到白色物件9之間的引用。

所指向的物件指的是:白色物件9。

都變成非白的:指的是白色物件9變成了灰色。

所以,在兩個條件順序觸發、物件圖掃描完成後會變成下面的樣子:

併發掃描結束之後,再以灰色物件9為根(把它作為根,自然會變成黑色),重新掃描一次,所以最終的物件圖變成了這樣:

有的小夥伴就會問了:如果在標記過程中,使用者執行緒並沒有把物件5指向物件9的操作,僅僅是發生了刪除物件6到物件9之間引用的操作,那麼這個物件圖是什麼樣子呢?

就是下面這個樣子,你應該可以想象出來:

物件9還是黑色,只是它變成了浮動垃圾,逃過了本次回收而已。並不影響程式執行。

接下來,讓上面的圖動起來,並且我把圖片之間的切換順序放慢。你再自己細品品:

所以,上面的全部描述,才是一次我認為正確的,展示SATB方案是如何解決“物件消失”問題的過程。

之前《面試官:你說你熟悉jvm?那你講一下併發的可達性分析》中對於這一部分的描述過於簡單,且存在錯誤,給大家道歉,並特以此文進行修正。

你是什麼時候的垃圾-勘誤

在《G1回收器:我怎麼知道你是什麼時候的垃圾?》這篇文章中有一句描述是這樣的:

“GC Roots 能直接關聯到的物件:就是一個 Region 已經使用過的部分,所以在 bottom 與 top 之間。”這句話是錯誤的。

實際上,通過文章後面的描述你也能發現。GC Roots 能直接關聯到的物件集合應該“小於” Region 已經使用過的部分,物件圖遞迴完之後,所有物件總和,才等於Region已經使用過的部分。

通過文章中後半部分的這個圖片也可以直觀的發現, bottom 到 top 之間是一個 Region 已經使用的部分。但是這一部分中,只有 bottom 到 NextTAMS 之間的物件才是 GC Roots 能直接關聯到的物件,這部分物件並不是一個 Region 已經使用過的部分。

你是什麼時候的垃圾-補充說明

關於《G1回收器:我怎麼知道你是什麼時候的垃圾?》這篇文章,還有兩個需要補充說明的地方。

有的讀者問說:文章中沒有討論回收的內容,每次清理不會真正回收,那是不是多輪標記後才發生一次回收呢?

一。

首先,文章中確實沒有討論回收相關的內容。我在前面部分也寫了,把G1回收切分為兩大部分:

1.Global Concurrent Marking:全域性併發標記。

2.Evacuation Pauses:該階段是負責把一部分Region裡的活物件拷貝到空Region裡面去,然後回收原本的Region空間。

只要清楚了全域性併發標記階段,就可以解答文中丟擲的這個問題:

所以我只說明了全域性併發標記階段。

如果想要了解回收階段的事,可以去看看R大的回答,強烈建議你看完本文,點個贊後,開啟下面的連結,反覆閱讀幾遍:

https://hllvm-group.iteye.com/group/topic/44381

其次,“每次清理不會真正回收,那是不是多輪標記後才發生一次回收呢?”

這句話,可能是我在文章強調了清理階段不拷貝任何物件,再加上沒有描述回收階段,導致讀者有點懵了吧。

一次全域性併發標記完成後,緊接著一次回收的過程。

只是G1收集器之所以能建立可預測的停頓時間模型(-XX:MaxGCPauseMillis指定,預設值為200毫秒),是因為它將 Region 作為單次回收的最小單元,即每次收集到的記憶體空間都是 Region 大小的整數倍,這樣就可以有計劃地避免在整個Java堆中進行全區域的垃圾回收。

更具體一點的做法就是每個 Region 裡面堆積的垃圾都有一個“價值”(價值即回收所獲得的空間大小以及回收所需要的時間的經驗值)。而這些“價值”,是維護在一個優先順序列表中的,G1收集器都是知道的。

所以回收階段會優先處理回收價值最大的那些 Region。因此,一次回收的過程並不會回收所有的 Region。

二。

這裡也就解釋了讀者提出的另外一個問題:如果每次標記完都會回收整理,那為什麼紅框所在的區間與上一次標記之後相同,好像沒有被整理一樣,整理之後不是應該不留下記憶體空隙嗎?

我覺得一個合理的解釋,就是我上面說的:這個 Region 的價值不夠,所以它本次沒有被回收。隨著時間的推移,它裡面堆積的垃圾越來越多,“價值”就越來越高,總是會被回收的。

還有讀者問:看了併發標記的過程,有個疑問 prevBitmap 的作用是什麼? 因為感覺每次都是從頭開始掃描,沒看到它的作用。

這個問題,可以從這張圖片入手解答:

這個 E 是 Remark 階段,可以看到,在這個階段,其實 PrevBitmap 是派上用場了。

前面剛剛說了,這個 Region 由於“價值”不夠,它逃過了上次垃圾回收,所以待到下次垃圾回收的時候,就是 prevBitmap 的用武之地了,它裡面記錄的地址對應的區間就不需要再次標記了,因為這些地址對應的物件就已經是垃圾了。

我們可以假設 E 代表的是第 n 輪迴收的過程的Remark階段。那麼 PrevBitmap 就是第 n-1 輪的標記結果。

之前的文章說了:一個 previous Bitmap 記錄的是上一輪 Concurrent Marking 後的物件標記狀態,因為上一輪已經完成(上一輪就是第n-1輪),所以這個bitmap的資訊可以直接使用。

可以直接使用的意思就是前面說的:它裡面記錄的地址對應的區間就不需要再次標記了,因為這些地址對應的物件就已經是垃圾了。

到 F 圖裡面,可以看到,當前的 F 圖是清理階段已經完成的狀態了:

判斷標準有二:

1.和 E 圖相比PrevBitmap 和 NextBitmap 已經交換了位置。

2.PrevBitmap 裡面對應的地址的空間已經被標記為淺灰色了。

這個時候已經完成標記,PrevBitmap 又變成了第n-1次標記的結果。

你是什麼垃圾-懟人

因為之前的文章已經發布了,所以我需要修改一下對應的內容。提醒後面的讀者,如果看到了文章,需要注意這些地方描述的有問題。

但是我在查詢我文章的過程中發現了一些讓我很鬱悶的事情,之前的文章,大都被剽竊了,我也見怪不怪,有時間就順手舉報一下了。

最過分的是下面這個:

這是一個百家號賬號,一字不差的抄我文章,還自己標註為“原創”?

我去寫了個評論:

他還不敢把評論放出來。

還有下面這個,你可長點心吧。你配的這張圖片,我倒是想在家拍,但是我拍不出來呀:

這樣的情況還有很多。說到底,就還是版權意識的問題。

版權問題,我之前在《訂閱號做了77天,我掙了487.52元》這篇文章裡面聊過:

我的號不會傳播任何盜版資源,以前如此,現在如此,以後也會如此。

不做惡,就是最大的善。與君共勉。

所以我在此鄭重宣告,如果未經許可轉載我的文章,必須標明原文地址,且保留文末公眾號二維碼,否則我一定見一個舉報一個。

我先舉報你涉黃,引起工作人員的注意,再舉報你抄襲,讓工作人員懲罰你。

氣死我了。

最後說一句(求關注)

通過這件事我也再次感覺到了,看網上的野生文章(比如我的),要持有謹慎、懷疑、學習的態度。

才疏學淺,難免會有紕漏,如果你發現了錯誤的地方,還請你留言給我指出來,我對其加以修改。(我每篇技術文章都有這句話,我是認真的說的。)

感謝您的閱讀,我堅持原創,十分歡迎並感謝您的關注。

我是why技術,一個不是大佬,但是喜歡分享,又暖又有料的四川好男人。

以上。

歡迎關注公眾號【why技術】,堅持輸出原創。分享技術、品味生活,願你我共同進步。

相關推薦

原創面試回去通知

這是why技術的第37篇原創文章 老規矩,先聊聊生活,上面這張圖片是我週一拍的。 週一晚上下班後發現公司樓下推著三輪車賣花的阿姨又開始買花了。整個路口只有她一個人在做生意,整條路上也沒有幾個人,大家都低著頭匆匆走著,繁花中帶著點憂傷。 於是,我去買了一把白玫瑰。 上週日把《霍亂時期的愛情》看完了,就剛好當

原創面試:談談對mysql聯合索引的認識?

引言 本文預計分為兩個部分: (1)聯合索引部分的基礎知識 在這個部分,我們溫習一下聯合索引的基礎 (2)聯合索引部分的實戰題 在這個部分,列舉幾個我認為算是實戰中的代表題,挑出來說說。 正文 基礎 講聯合索引,一定要扯最左匹配!放心,我不扯有的沒的,幾句話懂個大概就行! 最左匹配 所謂最左原則指的就是如果你

BAT面試題系列面試了解樂觀鎖和悲觀鎖嗎?

次數 catch val util overflow info 基本概念 因此 問題 前言 樂觀鎖和悲觀鎖問題,是出現頻率比較高的面試題。本文將由淺入深,逐步介紹它們的基本概念、實現方式(含實例)、適用場景,以及可能遇到的面試官追問,希望能夠幫助你打動面試官。 目錄

原創面試:講講mysql表設計要注意啥

引言 近期由於複習了一下mysql的內容,有些心得。隨手講其中一部分知識,都是一些煙哥自己平時工作的總結以及經驗。大家看完,其實能避開很多坑。而且很多問題,都是面試中實打實會問到的! 比如 OK,具體有下面這些問題 1、為什麼一定要設一個主鍵? 2、你們主鍵是用自增還是UUID? 3、主鍵為什麼不推薦有

Nginx面試給我講講Nginx如何實現四層負載均衡?

## 寫在前面 > 這次又被問到Nginx四層負載均衡的問題了,別慌,我們一起來細細分析這個看似簡單的問題。 > > 如果文章對你有點幫助,請關注 **冰河技術** 微信公眾號,點贊、在看、留言和轉發,大家的四連是我持續創作的最大動力。 負載均衡可以分為靜態負載均衡和動態負載均衡,接下來

因為我說volatile 是輕量級的 synchronized,面試讓我回去通知

# 因為我說:volatile 是輕量級的 synchronized,面試官讓我回去等通知! > volatile 是併發程式設計的重要組成部分,也是面試常被問到的問題之一。不要向小強那樣,因為一句:volatile 是輕量級的 synchronized,而與期望已久的大廠失之交臂。 volatile

因為不知道Java的CopyOnWriteArrayList,面試讓我回去通知

先看再點贊,給自己一點思考的時間,微信搜尋【沉默王二】關注這個靠才華苟且的程式設計師。本文 GitHub github.com/itwanger 已收錄,裡面還有一線大廠整理的面試題,以及我的系列文章。 hello,同學們,大家好,我是沉默王二,在我為數不多的面試經歷中,有一位姓馬的面試官令我印象深刻

兩萬字面試聽說很懂集合原始碼,接我二十道問題

問題一:看到這個圖,你會想到什麼? (PS:截圖自《程式設計思想》) 答: 這個圖由Map指向Collection的Produces並不是說Map是Collection的一個子類(子介面),這裡的意思是指Map的KeySet獲取到的一個檢視是Collection的子介面。 我們可以看到集合有兩個基本介面:

面經面試做過效能優化的工作嗎?會從哪些方面入手做效能優化呢?

## 寫在前面 > 隨著網際網路的高速發展,網際網路行業已經從IT時代慢慢步入到DT時代。對於Java程式設計師的要求越來越高,只是單純的掌握CRUD以不足以勝任網際網路公司的相關職位,大量招聘崗位顯示:如果是面試中高階的Java崗,基本上都需要懂效能優化的相關知識。今天,我們就一起來聊聊一個經典的面

搞定Jvm面試 面試談談 JVM 類檔案結構的認識

類檔案結構 一 概述 在 Java 中,JVM 可以理解的程式碼就叫做位元組碼(即副檔名為 .class 的檔案),它不面向任何特定的處理器,只面向虛擬機器。Java 語言通過位元組碼的方式,在一定程度上解決了傳統解釋型語言執行效率低的問題,同時又保留了解釋型語言可移植的特點。所以 Java 程式執行時比較高

搞定Jvm面試 面試談談 JVM 類載入過程是怎樣的?

類載入過程 Class 檔案需要載入到虛擬機器中之後才能執行和使用,那麼虛擬機器是如何載入這些 Class 檔案呢? 系統載入 Class 型別的檔案主要三步:載入->連線->初始化。連線過程又可分為三步:驗證->準備->解析。 載入 類載入過程的第一步,主要完成下面3件事情:

原創面試時遇到『看門狗』脖子上掛著『時間輪』,我就問怕不怕?

Redisson的看門狗和Netty的時間輪,瞭解一下?寫的過程中順便打了一下自己的臉。技術嘛,不就是在不斷打臉的過程中成長起來的嘛。 荒腔走板聊生活 大家好,一週的時間過的飛快,轉眼間又到週末了。 老規矩,還是本號特色,先是荒腔走板的聊聊生活。 上面的圖片是我在一次跑步的過程中拍的,一隻狗子。可以看到圖

Java面試靈魂拷問if語句執行完else語句真的不會再執行嗎?

## 寫在前面 > 最近跳槽找工作的朋友確實不少,遇到的面試題也是千奇百怪,這不,一名讀者朋友面試時,被面試官問到了一個直擊靈魂的問題:if 語句執行完else語句真的不會再執行嗎?這個奇葩的問題把這名讀者問倒了! ## 問題分析 最近一名讀者留言說,自己出去面試被面試官的一道奇葩問題問倒了,這個

Java8新特性面試談談Java8中的Stream API有哪些終止操作?

## 寫在前面 > 如果你出去面試,面試官問了你關於Java8 Stream API的一些問題,比如:Java8中建立Stream流有哪幾種方式?(可以參見:《[【Java8新特性】面試官問我:Java8中建立Stream流有哪幾種方式?](https://www.cnblogs.com/binghe

Spring註解驅動開發面試如何將Service注入到Servlet中?朋友又栽了

## 寫在前面 > 最近,一位讀者出去面試前準備了很久,信心滿滿的去面試。沒想到面試官的一個問題把他難住了。面試官的問題是這樣的:如何使用Spring將Service注入到Servlet中呢?這位讀者平時也是很努力的,看什麼原始碼啊、多執行緒啊、高併發啊、設計模式啊等等。沒想到卻在一個很簡單的問題上栽

MySQL面試問我MySQL如何實現無資料插入,有資料更新?我是這樣回答的

## 寫在前面 > 馬上就是金九銀十的跳槽黃金期了,很多讀者都開始出去面試了。這不,又一名讀者出去面試被面試官問了一個MySQL的問題:向MySQL中插入資料,如何實現MySQL中沒有當前id標識的資料時插入資料,有當前id標識的資料時更新資料。其實,這題目一點也不難!! ## 先來個簡單題目 正

高併發面試講講什麼是快取穿透?擊穿?雪崩?如何解決?

## 寫在前面 > 在前面的《[【高併發】Redis如何助力高併發秒殺系統?看完這篇我徹底懂了!!](https://mp.weixin.qq.com/s?__biz=Mzg3MzE1NTIzNA==&mid=2247487271&idx=1&sn=6bd9f4627357b1

高併發面試Java中提供了synchronized,為什麼還要提供Lock呢?

## 寫在前面 > 在Java中提供了synchronized關鍵字來保證只有一個執行緒能夠訪問同步程式碼塊。既然已經提供了synchronized關鍵字,那為何在Java的SDK包中,還會提供Lock介面呢?這是不是重複造輪子,多此一舉呢?今天,我們就一起來探討下這個問題。 ## 再造輪子? 既

高併發面試效能優化有哪些衡量指標?需要注意什麼?

## 寫在前面 > 最近,很多小夥伴都在說,我沒做過效能優化的工作,在公司只是做些CRUD的工作,接觸不到效能優化相關的工作。現在出去找工作面試的時候,面試官總是問些很刁鑽的問題來為難我,很多我都不會啊!那怎麼辦呢?那我就專門寫一些與高併發系統相關的面試容易問到的問題吧。今天,我們就來說說在高併發場景

效能優化面試Java中的物件都是在堆上分配的嗎?

## 寫在前面 > 從開始學習Java的時候,我們就接觸了這樣一種觀點:Java中的物件是在堆上建立的,物件的引用是放在棧裡的,那這個觀點就真的是正確的嗎?如果是正確的,那麼,面試官為啥會問:“Java中的物件就一定是在堆上分配的嗎?”這個問題呢?看來,我們從接觸Java就被灌輸的這個觀點值得我們懷疑