首先,截圖為證:
簡述:
本次滴滴面試,是之前那次滴滴面試掛了之后,另一個HR又撈起來的(還成了高級java崗位);
通過本次面試,本人深刻認識到了自己的不足(也許是高級java崗位所以面試難的原因?2年java的本人,自我感覺也就初中級java的水平);
通過本次面試,本人深刻認識到了自己與大佬的差距(看面試官的年齡感覺比本人大不了幾歲);
通過本次面試,本人發現了一條規律:什么事情都沒有看起來那么簡單,還得繼續努力。(只在網上背面試題是不夠的,面試官一問的深了就涼涼了,還得自己看源碼,自己多思考)
滴滴面試內容(主要看問題,答案暫時不標准):
1.自我介紹
大概介紹了下個人信息,工作項目。但是沒有深入詢問工作項目,一面的面試官主要不聊這個。
2.jvm講一下
講了棧、堆、元空間(jdk1.8前是方法區,1.8后是元空間);
新生代、老年代;
后期百度的答案: https://blog.csdn.net/yunzhaji3762/article/details/81038711
JVM是java虛擬機,用來實現java程序的跨平台運行;
java文件要編譯成class文件才能運行,jar包與war包中實際包含的也是class文件;
編譯命令,-d用來指定生成的class文件的位置
javac -d C:\test C:\test\Hello.java
執行命令,不用輸入.class
java Hello
.java源代碼->.class字節碼->類加載器->字節碼校驗器->解釋器與JIT代碼生成器->硬件
JVM體系結構分為:
類加載器(ClassLoader):用來裝載class文件
執行引擎:執行字節碼或執行本地方法
運行時數據區:方法區、堆、棧、PC寄存器、本地方法棧
JVM運行時數據區:
可以分為堆、棧、元空間(其中有常量池)、寄存器、本地方法棧。
PC寄存器:用於存儲每個線程下一步執行的JVM指令,如果該方法是本地方法(native),則PC寄存器中不存儲任何信息。
棧(stack):是線程私有的,每個線程創建一個棧,存放當前線程中的基本類型的局部變量(boolean,char,byte,short,int,long,float,double)、部分返回結果以及棧幀(stack frame),非基本類型的對象在JVM棧上僅存放一個指向堆的地址。
堆(heap):存儲對象實例以及數組值,可以認為所有通過new創建的對象的內存都在此分配,堆中的對象的內存需要等待GC回收。
●堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要加鎖,這也導致了new對象的開銷是比較大的。
new對象過多會導致堆溢出:java.lang.OutOfMemoryError: Java heap space
方法遞歸會導致棧溢出:StackOverflow
方法區/元空間:也稱為永久代;jdk1.8后方法區改名為元空間;存放了所加載的類的信息(名稱、修飾符等)、類中的靜態變量、類中定義的final類型的常量、類中的Field信息、類中的方法信息。
●jdk1.8后,元空間使用物理內存,不再使用JVM內存。
//常量池:存放類中固定的常量信息、方法和Field的引用信息等,其空間從方法區分配。
本地方法棧:支持本地方法運行,此區域存儲每個native方法調用的狀態。
3.什么對象會被移動到老年代?
多次GC后依然存活的對象。
4.什么類型的對象會參與GC?
new創建的、還有不知道什么。
百度的答案:
https://blog.csdn.net/qq_39839075/article/details/84867025
//由於無法避免對象相互引用,所以JVM不用這個:引用計數算法:給對象添加引用計數器,每次被引用就+1,引用失效就-1,當為0時就回收。
對象可達性算法:選擇一個GC ROOTS,當做根節點,向下尋找,能到達的對象就不回收,不能到達的對象就回收。
以下4種對象可以作為根節點:棧中引用的對象、元空間中靜態屬性所引用的對象、元空間中常量所引用的對象、本地方法棧中引用的對象;
注意靜態變量(static)、final常量不會被GC;而final變量是會被GC的,還有普通new的對象也會被GC。
5.GC機制?
百度的答案:
首先,堆中發生GC;
堆分為新生代、老年代、永久代(元空間),GC發生在新生代與老年代與永久代;
永生代也是可以回收的,條件是 1.該類的實例都被回收。 2.加載該類的classLoader已經被回收 3.該類不能通過反射訪問到其方法,而且該類的java.lang.class沒有被引用 當滿足這3個條件時,是可以回收,但回不回收還得看jvm。
永久代中也有廢棄常量(一段時間后沒有被使用的常量),如果有必要也會被回收。
新生代又分為3部分,一個eden,兩個survivor(from survivor、to survivor)。
https://blog.csdn.net/qq_36314960/article/details/79923581
大多數情況下,對象在新生代Eden區中分配,當Eden區沒有足夠空間進行分配時,虛擬機將發起一次Minor GC;同理,當老年代中沒有足夠的內存空間來存放對象時,虛擬機會發起一次Major GC/Full GC。只要老年代的連續空間大於新生代對象總大小或者歷次晉升的平均大小就會進行Minor GC,否則將進行Full GC。
虛擬機通過一個對象年齡計數器來判定哪些對象放在新生代,哪些對象應該放在老生代。如果對象在Eden出生並經過一次Minor GC后仍然存活,並且能被Survivor容納的話,將被移動到Survivor空間中,並將該對象的年齡設為1。對象每在Survivor中熬過一次Minor GC,年齡就增加1歲,當他的年齡增加到最大值15時,就將會被晉升到老年代中。虛擬機並不是永遠地要求對象的年齡必須達到MaxTenuringThreshold才能晉升到老年代,如果在Survivor空間中所有相同年齡的對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就可以直接進入老年代,無需等到MaxTenuringThreshold中要求的年齡。
當Java程序運行時,如果遇到大對象將會被直接存放到老生代中。如果老生代的空間也被占滿,當來自新生代的對象再次請求進入老生代時就會報OutOfMemory異常。新生代中的垃圾回收頻率高,且回收的速度也較快。
新生代約占堆大小的 1/3,老年代約占堆大小的 2/3;新生代較小,可以多觸發minor GC,並且此時要操作的對象也較少,效率較高;
當 JVM 無法為一個新的對象分配空間時會觸發 Minor GC,例如新生代的eden滿后觸發minor GC,之后將eden存活下來的對象移動到survivor,將survivor中符合條件的對象移動到老年代;
老年代滿后觸發Full GC,由於Full GC很慢,因此要盡量避免,調優時應盡量做到讓對象在Minor GC階段被回收、讓對象在新生代多存活一段時間及不要創建過大的對象及數組。
永久區滿后觸發Full GC,為避免永久區(Perm Gen)占滿造成Full GC現象,可采用的方法為增大Perm Gen空間或轉為使用CMS GC。
代碼中調用System.gc(),可以建議JVM執行FULL GC,但是最終不一定執行。
minor GC:清理年輕代。
//major GC = Full GC
Full GC:清理整個堆。
6.垃圾收集器?
不知道。
百度發現這篇文章說有7種:https://www.cnblogs.com/super-jing/p/10795099.html
7種垃圾收集器:
Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1;
垃圾收集器所處的位置:
新生代收集器:Serial、ParNew、Parallel Scavenge;
老年代收集器:Serial Old、Parallel Old、CMS;
整堆收集器:G1;
如何設置垃圾收集器:
https://www.cnblogs.com/Joy-Hu/p/10577103.html
啟動java應用時,使用-XX參數設置垃圾收集器
查看jvm默認使用的垃圾收集器的命令:
java -XX:+PrintCommandLineFlags -version
默認的垃圾收集器是-XX:+UseParallelGC,也就是說,Minor GC使用Parallel,Full GC使用Serial Old,具體見下方。
注意垃圾收集器不是最新的最好,需要考慮實際場景,選擇合適的垃圾收集器。
GC組合 |
Minor GC |
Full GC |
描述 |
-XX:+UseSerialGC | Serial收集器串行回收 | Serial Old收集器串行回收 | 該選項可以手動指定Serial收集器+Serial Old收集器組合執行內存回收 |
-XX:+UseParNewGC | ParNew收集器並行回收 | Serial Old收集器串行回收 | 該選項可以手動指定ParNew收集器+Serilal Old組合執行內存回收 |
-XX:+UseParallelGC | Parallel收集器並行回收 | Serial Old收集器串行回收 | 該選項可以手動指定Parallel收集器+Serial Old收集器組合執行內存回收 |
-XX:+UseParallelOldGC | Parallel收集器並行回收 | Parallel Old收集器並行回收 | 該選項可以手動指定Parallel收集器+Parallel Old收集器組合執行內存回收 |
-XX:+UseConcMarkSweepGC | ParNew收集器並行回收 | 缺省使用CMS收集器並發回收,備用采用Serial Old收集器串行回收 |
該選項可以手動指定ParNew收集器+CMS收集器+Serial Old收集器組合執行內存回收。優先使用ParNew收集器+CMS收集器的組合,當出現ConcurrentMode Fail或者Promotion Failed時,則采用ParNew收集器+Serial Old收集器的組合 |
-XX:+UseConcMarkSweepGC -XX:-UseParNewGC |
Serial收集器串行回收 | ||
-XX:+UseG1GC | G1收集器並發、並行執行內存回收 | 暫無 |
7.用過線程池嗎?講下線程池
講了線程池的4個參數。
8.如果線程池達到最大線程數,此時又來新線程,會怎樣?
9.線程等待隊列是怎么用的?什么時候會進入隊列?什么時候出隊列?
10.講下hashmap
講了hashmap結構,默認大小,jdk1.8后什么情況下轉為紅黑樹,如何擴容
11舉實際數字,問這時會不會擴容。
記住創建hashmap時實際大小是2的冪次、填充元素超過實際大小的0.75(如果不修改就是0.75)倍就會擴容即可。
12.詳細講下hashmap的put操作
計算key的hash值,如果沒有沖突,則放入元素;如果有沖突,說明目標位置有元素,則在已有元素后用鏈表的形式放入元素。
13.如果hash值大於hashmap的數組長度,怎么放入數組
當時沒答出來,(早)被問懵了。據說是取模運算就行。
14.如何實現線程安全?
synchronized、reentracklock
15.synchronized用法?
給方法上加、給代碼塊上加
16.synchronized給代碼塊上加,鎖住的是什么?(是用什么當鎖的)
可以用任意對象,string、class等都可以。
17.synchronized給方法上加,鎖住的是什么?
18.hashTable是怎么實現線程安全的?
put方法與get方法加synchronized
19.所以一個線程用put,一個線程用get,他們需要等待對方的鎖嗎?
正確答案是需要,要不怎么實現線程安全;所以synchronized給方法加鎖,鎖住的是類對象。(如果new HashTable(),就用這個hashTable對象當鎖,線程需要這個鎖才能執行synchronized標注的方法)
20.用20分鍾寫一個程序,一個二叉搜索樹,求最小差。例如:
35
29 90
20 34 47 101
此時,最小差是1.(35-34=1);
二叉搜索樹特點,左邊的小於根,右邊的大於根。
要求時間復雜度o(n),空間復雜度o(1)
當時考慮的思路:遍歷樹,用一個new int[1]數組來存儲差值,每個差都與數組比較、存更小的即可。
然而對樹的算法不熟悉,完成了50%吧。
后來才明白的思路:中序遍歷。
PS:據說Integer也可以存儲差值,在方法中改變它的值也有效果。(與int數組效果相同,不過當時互相不知道這個...)
總結:
面試官深入問了下jvm、線程池、hashmap、synchronized,再加一道二叉搜索樹求最小差的編程題;
很多都不會,有些不會的或者答錯的面試官會告訴你,雖然告訴了也不一定能聽懂;
整體就hashmap答的湊付能看,其余一塌糊塗——只背網上的根本不夠,稍微問深一點就不會;
面試時間累計1小時10分左右。
最后,問了下面試官正常技術面有幾面,面試官回答有三面;
問了下面試官面試沒有通過會有通知嗎?面試官說HR應該會通知。
其實不等通知也知道,又涼了唄。
----------------------------------------------------------------------
大概就問了這些,答案后續有空了完善。
然后就很迷,本人就是簡歷上的水平,簡歷初篩過了;
3個大廠面了4次(看前幾篇文章),都涼了……
這次明顯感覺比前幾次面的難,估計是高級的緣故;
如果有大佬感覺很簡單,那就是本人太菜了,本人怕不是個假的2年java(滑稽);
如果說前幾次是不明不白的涼,這次就是實實在在的涼了。
個人感覺,大廠一面基本都是這些概念,算法題,有淺有深。
想過一面,只看網上的文章不夠,還得自己翻源碼,自己思考理解。
任何事都沒有看上去那么簡單,成功沒有捷徑,還得不斷努力。
努力不一定能成功,但是不努力一定不會成功。