前言
我大概我是從去年12月份開始看書學習,到今年的6月份,一直學到看大家的面經基本上百分之90以上都會,我就在5月份開始投簡歷,邊面試邊補充基礎知識等。也是有些辛苦。終於是在前不久拿到了字節跳動的offer,現在我也來寫面經,希望能幫助到大家!
面經
Java基礎
0.HashMap的源碼,實現原理,JDK8中對HashMap做了怎樣的優化。
拉鏈結構,數組+鏈表,原理是hash找數組,沖突后拉鏈表,1.8優化為會進化成紅黑樹提高效率,並且使用2^n來做容量值
引申點:
- equal & hashcode
- 其他地方的hash處理,如redis的hash、集群slot等
- 對hash算法類型的了解(安全哈希和非安全哈希如mermerhash)
- 對hashMap實現的了解:取hashcode,高位運算,低位取模
- 一致性hash(處理了什么問題,在什么場景用到)
- 紅黑樹簡單描述
1.HaspMap擴容是怎樣擴容的,為什么都是2的N次冪的大小。
在容量到達抵達負載因子*最大容量的時候進行擴容,負載因子的默認值為0.75
2N的原因:
-
hash的計算是通過hashcode高低位混合然后和容量的length進行與運算
-
在length=2n的時候,與運算相當於是一個取模操作
-
那么在每次rehash完畢之后mod2N的意義在於要么該元素是在原位置,要么是在最高位偏移多一位的位置,提高效率
引申點:
- ConcurrentHashMap的擴容:1.7分段擴容以及1.8transfer並發協同的擴容
- redis漸進式hash擴容處理
3.HashMap,HashTable,ConcurrentHashMap的區別。
Map線程不安全(沒有用任何同步相關的原語),Table安全(直接加syn),Concurrent提供更高並發度的安全(分段鎖思想orSyn+Cas)
引申點:
- 對線程安全的定義:如hashmap在1.7前會頭插死循環,但是在1.8改善后還是不能叫線程安全,因為沒有可見性
- 對鎖粒度的思考:在介於map和table之間存在tradeoff之后的均衡解
- Syn和ReentranceLock的區別
- 鎖升級
4.極高並發下HashTable和ConcurrentHashMap哪個性能更好,為什么,如何實現的。
分兩種情況討論:
-
極高並發讀:並發讀的情況下,Table也為讀加了鎖,沒有並發可言,ConcurrentMap讀鎖並沒有加並發,直接可讀,若讀resize的某個tab為空則轉到新tab去讀,Node的元素val和指針next都是volatile修飾的,可以保證可見性,所以concurrentMap獲勝
-
極高並發寫:在並發寫的情況下,table也是直接加了Syn做鎖,強制串行,並且resize也只能單線程擴容,ConcurrentMap首先對於每個數組都有並發度,其次在resize的時候支持多線程協同,所以concurrentMap獲勝
所以整體而言concurrentMap優勢在於:
-
讀操作基於volatile可見性所以無鎖
-
寫操作優勢在於一是粗粒度的數組鎖,二是協同resize
這個問題的思路是先分類討論然后描述細節最后在下結論
引申點:
- volatile的實現:保證內存可見、禁止指令重排序但無法保證原子性
- java內存模型
- JVM做的並行優化、先行發生原則與指令重排序
- 底層細節的熟悉
5.HashMap在高並發下如果沒有處理線程安全會有怎樣的安全隱患,具體表現是什么。
1.7前死鎖,1.7后線程會獲取臟值導致邏輯不可靠
6.java中四種修飾符的限制范圍。
public:公用,誰來了都給你用
protected:包內使用,子類也可使用
default:包內使用,子類不可使用
private:自己用
7.Object類中的方法。
wait\hashcode\equal\wait\notify\getclass\tostring\nofityall\finalize
引申點:
- wait和sleep區別
- hashcode存在哪兒(對象頭里)
- finalize作用:GC前執行,但是不一定能把這個函數跑完
- getClass后能獲取什么信息:引申到反射
8.接口和抽象類的區別,注意JDK8的接口可以有實現。
接口:可以imp多個接口,1.7之前不允許實現,1.8后可以實現方法
抽象類:只能繼承一個類,抽象類中可以存在默認實現方法
接口的語義是繼承該接口的類有該類接口的行為
抽象類的語義是繼承該抽象類的類本身就是該抽象類
9.動態代理的兩種方式,以及區別。
-
CGLIB:其本質是在內存中繼承了一個子類,可以代理希望代理的那個類的所有方法
-
JDK動態代理:實現InvocationHandler,通過生成一個Proxy來反射調用所有的接口方法
優劣:
-
CGLIB:會在內存中多存額外的class信息,對metaspace區的使用有影響,但是性能好,可以訪問非接口的方法
-
JDK動態代理:本質是生成一個繼承所有接口的Proxy來反射調用方法,局限性在於其只能代理接口的方法
引申點:
- Spring的AOP實現以及應用場景
- 反射的開銷:檢查方法權限,序列化以及匹配入參
- ASM
10.Java序列化的方式。
繼承Serializable接口並添加SerializableId(idea有組件可以直接生成),ID實際上是一個版本,標志着序列化的結構是否相同
11.傳值和傳引用的區別,Java是怎么樣的,有沒有傳值引用。
本質上來講Java傳遞的是引用的副本,實際上就是值傳遞,但是這個值是引用的副本,比如方法A中傳入了一個引用ref,那么在其中將ref指向其他對象並不影響在方法A外的ref,因為ref在傳入方法A的時候實際上是指向同一個對象的另一個引用,可以稱之為ref',ref'若直接修改引用的對象會影響ref,但若ref'指向其他對象則和ref沒有關系了
12.一個ArrayList在循環過程中刪除,會不會出問題,為什么。
分情況討論:
-
fori刪除,不會直接拋異常,但是會產生異常訪問
-
foreach刪除(實際就是迭代器),會直接拋出並發修改異常,因為迭代器會進行獲取迭代器時的exceptModCount和真實的modCount的對比
引申點:
- 迭代器實現
- ArrayList內部細節
13.@transactional注解在什么情況下會失效,為什么。
方法A存在該注解,同時被方法B調用,外界調用的是Class.B的方法,因為內部實際上的this.a的調用方式沒走代理類所以不會被切面切到
數據結構和算法
1.B+樹
出度為m的一顆樹,節點的子女在[M/2,M]之間
葉子節點存儲全量信息
非葉子節點只充當索引進行葉子節點的路由(內存友好、局部性友好)
底層的葉子節點以鏈表的形式進行相連(范圍查找友好)
2.快速排序,堆排序,插入排序(其實八大排序算法都應該了解
快排:核心是分治logn
堆排:基於二叉樹nlogn
插入:暴力n2
3.一致性Hash算法,一致性Hash算法的應用
一致性hash,將整個hash的輸出空間當成一個環,環中設立多個節點,每個節點有值,當對象的映射滿足上個節點和這個節點中間值的時候它就落到這個節點當中來
應用:redis緩存,好處是平滑的數據遷移和快速的rebalance
引申點:
- 一致性hash熱點怎么處理:虛擬節點
- redis如何實現的:客戶端尋址
JVM
1.JVM的內存結構。
程序計數器:計算讀到第幾行了,類似一個游標
方法棧:提供JVM方法執行的棧空間
本地方法棧:提供native方法執行的棧空間
堆:存對象用的,young分eden,s0,s1,分配比例大概是8:1:1,Old只有一個區
方法區:1.8后為metaspace,存class信息,常量池(后遷移到堆中),編譯出來的熱點代碼等
引申點:
- heap什么時候發生溢出
- stack什么時候發生溢出
- 方法區什么時候發生溢出
- hotspot code的機制
- 流量黑洞如何產生的
2.JVM方法棧的工作過程,方法棧和本地方法棧有什么區別。
方法棧是JVM方法使用的,本地方法棧是native方法使用的,在hotspot其實是用一個
3.JVM的棧中引用如何和堆中的對象產生關聯。
引用保存地址,直接可以查找到堆上對應地址的對象
4.可以了解一下逃逸分析技術。
方法中開出來的local變量如果在方法體外不存在的話則稱之為無法逃逸
-
可以直接分配在棧上,隨着棧彈出直接銷毀,省GC開銷
-
消除所有同步代碼,因為本質上就是個單線程執行
引申點:
JVM編譯優化:
- 逃逸分析
- 棧上分配
- 分層編譯與預熱
- 棧上替換
- 常量傳播
- 方法內聯
...
5.GC的常見算法,CMS以及G1的垃圾回收過程,CMS的各個階段哪兩個是Stop the world的,CMS會不會產生碎片,G1的優勢。
常見算法:
-
標記清楚:存在內存碎片,降低內存使用效率
-
標記整理:整理可分為復制整理和原地整理,不存在內存碎片,但是需要額外的cpu算力來進行整理,若為復制算法還需要額外的內存空間
CMS流程:
-
初始標記(stw):獲得老年代中跟GCRoot以及新生代關聯的對象,將其標記為root
-
並發標記:將root標記的對象所關聯的對象進行標記
-
重標記:在並發標記階段,並沒有stw,所以會有一些臟對象產生,即標記完畢之后又產生關聯對象修改
-
最終標記(stw):最終確定所有沒有臟對象的存活對象
-
並發清理:並發的清理所有死亡對象
-
Reset:重設程序為下一次FGC做准備
CMS優劣:
-
優點:
-
不像PN以及Serial一樣全程需要stw,只需要在兩個標記階段stw即可
-
並發標記、清楚來提升效率,減少stw的時間和整體gc時間
-
在最終標記前通過預設次數的重標記來清理臟頁減少stw時間
-
-
缺點:
-
仍然存在stw
-
基於標記清楚算法的GC,節省算力但是會產生內存碎片
-
並發標記和清楚會造成cpu的高負擔
-
G1流程:
這個我只懂個大概,如下
分塊分代回收,可分為youngGC和MixedGC,特點是可預測的GC時間(即所謂的軟實時特性)
引申點:
- 是否進行過線上分析
- GC日志是否讀過,里面有什么信息
- 你們應用的YGC和FGC頻率以及時間是多少
- 你清楚當前應用YGC最多的一般是什么嗎
業務相關:
- 在線上大部分curd業務當中,實際上造成ygc影響較嚴重且可優化的是日志系統
- 對dump出來的堆進行分析的話里面有很大一塊是String,而其中大概率會是日志中的各種入參出參
- 優化方案有很多:
* 將不需要打日志的地方去除全量日志打印功能 * 日志在不同環境分級打印 * 只打出錯誤狀態的日志 * 在大促期間關閉非主要日志打印 * 同步改異步等
6.標記清除和標記整理算法的理解以及優缺點。
上文已答
7.eden survivor區的比例,為什么是這個比例,eden survivor的工作過程。
8:2
定性的來講:大部分對象都只有極短的存活時間,基本就是函數run到尾就釋放了,所以給新晉對象的buffer需要占較多的比例,而s區可以相對小一點來容納長時間存活的對象,較小的另一個原因是在幾次年齡增長后對象會進入老年代
定量的來講:實驗所得,也可以根據自己服務器的情況動態調整(不過筆者沒調過)
8.JVM如何判斷一個對象是否該被GC,可以視為root的都有哪幾種類型。
沒有被GCRoot所關聯
Root對象:(tips:不用硬記,針對着JVM內存區域來理解即可)
-
函數棧上的引用:包括虛擬機棧和native棧
-
static類的引用:存在方法區內
-
常量池中的常量:堆中
引申點:
- gc roots和ref count的區別
9.強軟弱虛引用的區別以及GC對他們執行怎樣的操作。
強:代碼中正常的引用,存在即不會被回收
軟:在內存不足的時候會對其進行GC,可用於緩存場景(類似redis淘汰)
弱:當一個對象只有弱引用關聯的時候會被下一次GC給回收
虛:又稱幽靈引用,基本沒啥用,在GC的時候會感知到
引申點:
- 每個引用的使用場景
- 是否在源碼或者項目中看到過or使用過這幾種引用類型(ThreadLocal里用了WeakReference)
10.Java是否可以GC直接內存。
在GC過程中如果發現堆外內存的Ref被使用則GC
11.Java類加載的過程。
12.雙親委派模型的過程以及優勢。
13.常用的JVM調優參數。
14.dump文件的分析。
15.Java有沒有主動觸發GC的方式(沒有)。
多線程
1.Java實現多線程有哪幾種方式。
2.Callable和Future的了解。
3.線程池的參數有哪些,在線程池創建一個線程的過程。
4.volitile關鍵字的作用,原理。
5.synchronized關鍵字的用法,優缺點。
6.Lock接口有哪些實現類,使用場景是什么。
7.可重入鎖的用處及實現原理,寫時復制的過程,讀寫鎖,分段鎖(ConcurrentHashMap中的segment)
8.悲觀鎖,樂觀鎖,優缺點,CAS有什么缺陷,該如何解決。
9.ABC三個線程如何保證順序執行。
10.線程的狀態都有哪些。
11.sleep和wait的區別。
12.notify和notifyall的區別。
13.ThreadLocal的了解,實現原理。
數據庫相關
1.常見的數據庫優化手段
-
log同步刷盤改異步刷盤
-
集群的話強雙寫改異步同步
-
針對sql優化(explain慢sql)
-
添加索引
2.索引的優缺點,什么字段上建立索引
優點:查的快,支持range
缺點:大部分查詢實際需要回表,索引建立會額外消耗內存和磁盤,對開發者的sql也有要求
字段:區分度大的字段
3.數據庫連接池。
4.durid的常用配置。
計算機網絡
1.TCP,UDP區別。
2.三次握手,四次揮手,為什么要四次揮手。
3.長連接和短連接。
4.連接池適合長連接還是短連接。
設計模式
1.觀察者模式
2.代理模式
舉例子JDK動態代理,通過一層proxy對真實對象進行代理,進行一些額外操作(e.g.:增強行為、負載均衡等)
3.單例模式,有五種寫法,可以參考文章單例模式的五種實現方式
-
普通單例
-
lazyloading+syn單例
-
lazyloading+doublecheck單例
-
枚舉
-
最后一種不知道,查了發現是靜態內部類單例,利用靜態內部類第一次訪問才加載的機制實現lazyloading
4.可以考Spring中使用了哪些設計模式
分布式相關
1.分布式事務的控制。
2.分布式鎖如何設計。
3.分布式session如何設計。
4.dubbo的組件有哪些,各有什么作用。
duboo不熟悉
5.zookeeper的負載均衡算法有哪些。
zookeeper就會個zab,不過負載均衡無非是公平輪詢、加權輪詢、隨機輪詢或者維護某些資源信息的動態路由這幾種
6.dubbo是如何利用接口就可以通信的。
不太熟,估計涉及到服務注冊以及序列化反序列化相關內容
緩存相關
1.redis和memcached的區別。
2.redis支持哪些數據結構。
3.redis是單線程的么,所有的工作都是單線程么。
4.redis如何存儲一個String的。
5.redis的部署方式,主從,集群。
6.redis的哨兵模式,一個key值如何在redis集群中找到存儲在哪里。
7.redis持久化策略。
框架相關
1.SpringMVC的Controller是如何將參數和前端傳來的數據一一對應的。
2.Mybatis如何找到指定的Mapper的,如何完成查詢的。
3.Quartz是如何完成定時任務的。
4.自定義注解的實現。
5.Spring使用了哪些設計模式。
6.Spring的IOC有什么優勢。
7.Spring如何維護它擁有的bean。
一些較新的東西
1.JDK8的新特性,流的概念及優勢,為什么有這種優勢。
2.區塊鏈了解
3.如何設計雙11交易總額面板,要做到高並發高可用。
最后
希望我總結的這些東西對你們會有幫助,你們看完之后有什么的不懂的歡迎在下方留言討論,也可以私信問我,我一般看完之后都會回的,也可以關注我的公眾號:前程有光,馬上金九銀十跳槽面試季,整理了1000多道將近500多頁pdf文檔的Java面試題資料,文章都會在里面更新,整理的資料也會放在里面。