面試
- 一般都是由淺到深去問,思路是:
- 先考察基礎是否過關,因為基礎知識決定了一個技術人員發展的上限
- 再通過深度考察是否有技術熱情和深度以及技術的廣度
- 同時可能會提出一些質疑和挑戰來考察候選人能否與有不同意見的人溝通
考察內容和方式
基礎知識
- 技術上深度與廣度兼顧
- 基礎知識: 考察基礎的時候一般都不會深入地去問,主要目的是考察知識面
- 計算機理論基礎:
- 計算機網絡
- 數據結構
- 計算機組成原理
- 計算機操作系統
- JDK:
- 源碼
- 集合
- BIO或者NIO
- annotation等
- JVM:
- 內存模型
- 類加載原理
- 數據庫:
- 索引
- 事務
- 死鎖等
- 並發:
- 並發的優缺點
- 內存可見性 - volatile
- 鎖
- 同步
- 線程池框架
- 網絡:
- TCP
- HTTP
- 常見設計模式
- 計算機理論基礎:
深入考察
- 深入考察:
- 深入考察不會像考察基礎時面面俱到,而是會在某個點上深入去聊
- 這個點可以是讓候選人自己選一個點,也可能面試官根據簡歷內容去選
- 主要目的是考察候選人對某個技術點的深入掌握程度
- 技術是相通的,如果一個人能在某個技術點上達到很深入的程度,其他點上也不會有太大問題,如果某個人在聲稱很了解的技術點上都支支吾吾,一知半解,多半可以判斷此人要么技術有限,要么遇到問題不願深入考察
- 考查工程師的工程能力,比如: 做過哪些項目? 遇到最難的問題是什么? 怎么解決的? 說說最有成就感的一項任務
- 深入考察的技術點:
- Java框架:
- Spring源碼的AOP和IOC
- JDK:
- ConcurrentHashMap如何提高並發度
- NIO的原理,包括零拷貝,堆外內存以及優缺點
- 虛擬機:
- 包沖突,類沖突的形成原理以及解辦法,可以引申到JDK 9的模塊化設計
- TCCL的存在價值
- 服務器:
- Tomcat源碼
- Netty源碼
- 數據結構:
- 數組
- 鏈表
- 樹
- 圖
- 排序
- 算法
- 分布式:
- 緩存應用
- 一致性哈希
- RPC原理和設計 - 通信協議,序列化方式,超時機制等
- 負載均衡
- 分布式緩存架構設計
- 分布式消息
- 消息隊列設計與使用
- 分布式環境下中間件部署
- 分布式事務
- paxos
- 中間件:
- MQ
- ES
- 數據庫:
- 數據庫性能優化 - 慢SQL,索引優化,大事務,內核參數調優
- MySQL數據庫底層原理
- 工作中遇到的數據庫問題以及解決辦法
- Redis
- 並發:
- 非阻塞鎖CAS
- 並發對編譯器優化的影響
- 線程池調優
- 工作中遇到的並發問題以及解決辦法
- 技術趨勢:
- Docker
- 微服務
- Java框架:
業務相關
- 做的項目所使用的技術棧以及其中的優缺點?
- 如果從零開始,能不能重新將其實現?
- 當前系統的使用方是誰? 用戶量多大? 用戶集中使用的時間點?
- 系統落下了哪些數據? 這些數據的使用方是誰? 系統的依賴方是誰?
- 從技術,產品,業務的角度去畫下相關的流程圖
工作交接
- 和產品,業務,運營,技術之間的工作交接:
- 知道自己的職責邊界
- 弄清楚哪些是自己需要做的
- 哪些是其余的人應該做的
- 交流起來不卑不亢
面試准備
- 面試最好的准備方式:
- 一定是平時多多積累
- 遇到問題不要逃避
- 深入去思考並解決
- 在解決一個個問題的過程中積累解決問題的能力,形成自己的知識體系
- 如何提前准備將自己平時積累展現出來:
- 針對面試考察內容知識點思考答案以及擴展.如果能知道大部分,就要更加深入一部分.這個過程主要是整理自己的知識體系
- 回憶整理簡歷和過往項目中的"難點","亮點",因為這是用來區分候選人的重要的點.面試一定會問"在項目中經歷最大的技術難點是什么?" 整理一下思路,不至於在面試時因為時間久遠而回憶不起來細節影響面試效果
- 溝通過程要做到有理有據,不卑不亢.在技術問題上堅持自己的客觀和原則,根據共同認可的事實進行邏輯判斷得出觀點
面試內容
Java基礎
- private修飾的方法可以通過反射訪問,那么private意義是什么?
- 考查對Java設計的掌握程度
- Java的private修飾符並不是為了絕對安全性設計的,更多的是對用戶常規使用Java的一種約束
- 從外部對對象進行常規調用時,可以清晰了解類結構
- Java中如何利用反射獲取一個類的字段?
- 常見的類加載
- Java類的初始化順序?
- Java類的初始化順序:
- 基類靜態代碼塊,基類靜態成員變量(並列優先級,按照代碼中出現的先后順序執行,並且只有第一次加載時執行)
- 派生類靜態代碼塊,派生類靜態成員變量(並列優先級,按照代碼中出現的先后順序,並且只有第一次加載時執行)
- 基類普通代碼塊,基類普通成員變量(並列優先級,按照代碼中出現的先后順序執行)
- 基類構造函數
- 派生類普通代碼塊,派生類普通成員變量(並列優先級,按照代碼中出現的先后順序執行)
- 派生類構造函數
- 局部變量使用前需要顯式賦值,否則編譯通過不了,為什么需要這么設計?
- 成員變量:
- 可以不經初始化,在類的加載過程中的准備階段可以賦予默認值
- 賦值和取值訪問的先后順序具有不確定性
- 成員變量可以在一個方法調用前賦值,也可以在方法調用后進行賦值. 這是在運行時發生的,編譯器確定不了,所有交給JVM來賦值
- 局部變量:
- 在使用之前需要顯式賦予初始值
- 局部變量的賦值和訪問順序是確定的
- 這樣設計是一種約束,盡最大可能減少使用者犯錯:
- 假使局部變量可以使用默認值,可能總會無意間忘記賦值,進而導致不可預期的情況發生
- ArrayList,LinkList的區別?插入和查找哪個更快?
- ArrayList的隨機插入?
- LinkList是單向鏈表還是雙向鏈表?
- 單向鏈表如何實現一個棧?
- HashMap的put操作底層是如何實現的?
- HashTable,ConcurrentHashMap的區別?為什么ConcurrentHashMap並發度更高?
- ConcurrentHashMap中的get和set什么時候會加鎖?如果不加鎖會產生什么問題?什么時候是CAS操作?
- HashMap的原理,在Java 8中做了哪些改變?
- 從結構實現上來講:
- HashMap實現是數組+鏈表+紅黑樹(紅黑樹部分是JDK 1.8之后增加的)
- HashMap最多允許一條記錄的鍵為null,允許多條記錄的值為null
- HashMap是非線程安全的
- String與StringBuilder的區別?
- 可變與不可變:
- String不可變,每一次執行 "+" 都會新生成一個新對象,所以頻繁改變字符串的情況下不用String,以節省內存
- 是否多線程安全:
- StringBuilder並沒有對方法進行加同步鎖,所以是非線程安全的.StringBuffer和String都是線程安全的
- Vector和Array的區別?
- ArrayList在內存不夠時默認擴展是50%+1個,Vector默認是擴展1倍
- Vector是屬於線程安全級別的,但是大多數情況下不使用Vector,因為線程安全需要更大的系統開銷
- HashMap和HashTable的區別?
- HashTable繼承Dictionary類,HashMap繼承AbstrctMap類
- HashTable不允許空鍵值對,而HashMap允許空鍵值對,但最多只有一個空對象
- HashTable同步,而HashMap不同步,效率上比HashTable要高
- ConcurrentHashMap和HashTable比較,兩個線程並發訪問Map中同一條鏈,一個線程在尾部刪除,一個線程在前面遍歷查找.為什么前面的線程還能正確的查找到后面被另一個線程刪除的節點?
- ConcurrentHashMap融合了HashTable和HashMap二者的優勢:
- HashTable是做了同步的,是線程安全的,而HashMap未考慮同步,所以HashMap在單線程情況下效率比較高
- HashTable在多線程的情況下,同步操作能保證程序執行的正確性
- 但是HashTable是阻塞的,每次同步執行的時候都要鎖住整個結構
- ConcurrentHashMap正好解決了效率和阻塞問題:
- ConcurrentHashMap允許多個修改操作並發進行,技術的關鍵是使用了鎖分離,即一個Array保存多個Object,使用這些對象的鎖作為分離鎖,get或者put的時候隨機使用任意一個
- ConcurrentHashMap使用了多個鎖來控制對Hash表的不同部分進行的修改
- 從JDK 1.6開始,在HashEntry結構中,每次插入將新添加節點作為鏈的頭節點,這與HashMap實現相同.
- 每次刪除一個節點時,會將刪除節點之前的所有節點拷貝一份組成一個新的鏈,而將當前節點的上一個節點的next指向當前節點的下一個節點.從而在刪除以后會有兩條鏈存在
- 因此可以保證即使在同一條鏈中,有一個線程在刪除,而另一個線程在遍歷,都能工作良好.因為遍歷的線程能繼續使用原有的鏈
- 在Java 8中,使用volatile HashEntry保存數據,table元素作為鎖.從Table數組+單向鏈表又加上了紅黑樹
- 紅黑樹是一種特別的二叉查找樹,紅黑樹的特性:
- 節點為紅或黑
- 根節點為黑
- 葉節點為黑
- 一節點為紅,則一節點為黑
- 一節點到其子孫節點所有路徑上的黑節點數目相同
- ArrayList和LinkedList的區別?
- ArrayList底層的數據結構是數組,支持隨機訪問.LinkedList的底層數據結構是鏈表,不支持隨機訪問
- 使用下表訪問一個元素:
- ArrayList的時間復雜度是O(1)
- LinkedList的時間復雜度是O(n). LinkedList是雙向鏈表
- HashMap中put()元素產生沖突,為什么使用LinkedList拉鏈法解決而不用ArrayList解決?產生沖突時key值不等,新元素怎么樣加入鏈表?為什么這么設計?
- Java中的Comparator和Comparable有什么不同?
- Comparable接口用於定義對象的自然順序,是排序接口
- Comparator通常用於定義用戶定制的順序,是比較接口
- 如果需要控制某個類的次序,而該類本身不支持排序,即沒有實現Comparable接口,就可以建立一個"該類的比較器"來進行排序
- Comparable總是只有一個,但是可以有多個Comparator來定義對象的順序
- 抽象類是什么?與接口有什么區別?為什么要使用抽象類?
- 抽象類是不允許被實例化的類,一個類只能使用一次繼承關系,但是一個類可以實現多個接口
- 抽象類和接口所反映出的設計理念不同:
- 抽象類表示的是 "is - a"
- 接口表示的是 "like - a"
- 實現抽象類和接口的類必須實現其中的所有方法.抽象類可以有非抽象方法,接口中則不能有實現方法,但是在Java 8中允許接口中有靜態默認方法
- 接口中定義的變量默認是public static final型,且必須給出初值,所以實現類中不能重新定義,也不能改變這個值
- 抽象類中定義的變量默認是friendly型,這個變量的值可以在子類中重新定義,也可以重新賦值
- 子類中實現父類中的抽象方法時.可見性可以大於等於父類中的
- 接口實現類類中的接口方法的可見性只能與接口中的相同,即為public
- 使用抽象類是為了重用,減少編碼量,降低耦合性
- Java中的重載和重寫?
- 重載和重寫都是使用相同的名稱實現不同的功能,但是重載是編譯時活動,重寫是運行時活動
- 可以在同一個類中重載方法,但只能在子類中重寫方法,重寫必須要有繼承
- 重載:
- 重載的時候,方法名要一樣,但是參數類型和參數個數不一樣,返回值類型可以相同也可以不同
- 無法以返回型別作為重載函數的區分標准
- 重寫:
- 在子類中可以根據需要對從基類中繼承的方法進行重寫
- 重寫的方法和被重寫的方法必須具有相同的方法名稱,參數列表和返回類型
- 重寫方法不能使用比被重寫方法更嚴格的訪問權限
- Collection和Collections的區別是什么?
- Collection< E >是Java集合框架中的基本接口
- Collections是Java集合框架提供的一個工具類,其中包含了大量用於操作和返回集合的靜態方法
- Java中多態的實現原理?
- 多態指的是父類引用指向子類的對象,調用方法時會調用子類的實現而不是父類的實現
- 多態的實現關鍵在於動態綁定
- Object中定義了哪些方法?
- clone()
- equals()
- hashCode()
- toString()
- notify()
- notifyAll()
- wait()
- finalize()
- getClass()
- Java中的泛型和類型擦除?
- 泛型即參數化類型,在創建集合時,指定集合元素的類型,此集合只能傳入該類型的參數
- 類型擦除:Java編譯器生成的字節碼不包括泛型信息,所以在編譯時擦除
- 泛型用最頂級的父類替換
- 移除
- JDK 1.8引入的新特性?
- Lambda表達式
- 允許像對象一樣傳遞匿名函數Stream API,充分利用現代多核CPU,可以寫出很簡潔的代碼
- Date與Time API,有一個穩定簡單的日期和時間庫可供使用
- 接口中可以有靜態,默認方法
- 重復注解,可以將相同的注解在同一類型上使用多次
- Java中public,private,protected以及默認關鍵字的訪問范圍?
- protected可在包內及包外子類訪問
- default只能在同一包內訪問
- private只能在同一個類中訪問
- 常用的數據結構?
- 集合
- 線性結構
- 數組
- 隊列
- 鏈表
- 棧
- 樹形結構
- 圖狀結構
- Java中的TreeMap是采用什么樹實現的?
Java中的TreeMap是使用紅黑樹實現的
- 匿名內部類是什么?如何訪問在匿名內部類外面定義的變量?
- 匿名內部類就是沒有名字的內部類,匿名內部類只能使用一次,通常用來簡化代碼編寫
- 匿名內部類只能訪問外部類的final變量
- 在Java 8中,如果局部變量被匿名內部類訪問,那么該局部變量相當於自動使用了final修飾
- 如何高效地創建一個線程安全的單例模式?
- 通過枚舉
- 通過靜態內部類
- 也可以通過雙重檢查創建單例模式,但是這種單例模式是線程不安全的
- poll()方法和remove()方法的區別?
- poll()和remove都是從隊列中取出一個元素
- poll()在獲取元素失敗時會返回空
- remove()在獲取元素失敗時會拋出異常
- 寫一段代碼在遍歷ArrayList時移除一個元素?
- 使用迭代器
Iterator it = list.iterator(); while (it.hasNext()) { if (...) { it.remove(); } }
- 注解的原理
- 開源協議哪些?
- GPL: GNU General Public License,GNU通用公共許可協議
- LGPL: GNU Lesser General Public License, GNU寬通用公共許可協議
- BSD: Berkeley Software Distribution, 伯克利軟件分發許可協議
- MIT: Massachusetts Institute of Technology
- Apache: Apache Licence, Apache許可協議
- MPL: Mozilla Public Licence, Mozilla公共許可協議
線程
- 線程同步與阻塞關系?同步一定阻塞嗎?阻塞一定同步嗎?
- 線程同步與否和阻塞非阻塞沒有關系
- 同步是一個過程,阻塞是線程的一種狀態
- 多個線程操作共享變量時會出現競爭
- 需要使用同步來防止兩個以上的線程同時進入臨界區內,在這個過程中,后進入臨界區的線程將阻塞,等待先進入的線程走出臨界區
- 同步和異步有什么區別?
- 同步和異步最大的區別是: 一個需要等待,一個不需要等待
- 同步可以避免出現死鎖,讀臟數據的發生,一般共享某一資源的時候使用
- 如果每個人都有修改權限,同時修改一個文件,有可能使一個人讀取另一個人已經刪除的內容,就會出錯
- 同步就會按照順序來修改
- 線程池?
- 線程池的作用是根據系統自身的情況,有效的限制執行線程的數量,使得運行效果達到最佳
- 線程池主要執行的是:
- 控制執行線程的數量
- 超出數量的線程排隊等候
- 等待有任務執行完畢
- 再從隊列中最前面取出任務執行
- 如何調用wait()方法?使用if塊還是循環?為什么?
- wait()方法應該在循環中調用:
- 因為當線程獲取到CPU開始執行的時候,其他條件可能還沒有滿足
- 所以在處理前,循環檢測條件是否滿足更好
- wait(),notify()和notifyAll()方法是java.lang.Object類為線程提供的用於實現線程間通信的同步控制的等待和喚醒方法
- 實現線程的幾種方法?
- 實現線程的方法:
- 繼承Thread類,重寫run函數
- 實現Runnable接口,重寫run函數
- 實現Callable接口,重寫call函數
- 什么是多線程環境下的偽共享 - false sharing?
- 偽共享是多線程系統(這個系統的每隔處理器都有自己的局部緩存)中一個普遍存在的性能問題
- 緩存系統中是以緩存行(cache line)為單位存儲的
- 緩存行是2的整數冪個連續字節,一般為32 - 256字節
- 最常見的緩存行是64個字節
- 當多線程修改相互獨立的變量時,如果這些變量共享同一個緩存行,就會影響彼此的性能,這就是偽共享
- 線程的狀態?
- concurrent包下面,都用過哪些包?
- java.util.concurrent
- java.util.concurrent.atomic
- java.util.concurrent.lock
- ReadWriteLock讀寫之間互斥嗎?
- ReadWriteRock讀寫鎖的使用場景:
- 讀 - 讀
- 讀 - 寫
- 寫 - 寫
- 除了讀 - 讀之間是共享的,其余都是互斥的
- 怎樣實現互斥鎖和同步鎖
- 考查對AQS, CAS的掌握程度
- Java並發編程中的輔助類?
- Semaphore
- CountDownLatch
- CyclicBarrier
- Exchanger
- CountDownLatch和CyclicBarrier之間的區別?
- ReadWriteLock之間互斥嗎?
- ReadWriteLock讀寫鎖的使用場景:
- 讀,讀
- 讀,寫
- 寫,寫
- 除了讀和讀之間是共享的,其他都是互斥的
這樣之后會討論怎樣實現互斥鎖和同步鎖的,了解對AQS,CAS的掌握程度,技術學習深度
- Semaphore拿到執行權的線程之間是否互斥?
- Semaphore拿到執行權的線程之間是互斥的
- Semaphore, CountDownLatch, CyclicBarrier, Exchanger是Java並發編程中的4個輔助類,了解CountDownLatch和CyclicBarrier之間的區別
- Semaphore可能有多把鎖,可以允許多個線程同時擁有執行權,這些有執行權的線程如果並發訪問同一對象,會產生線程安全問題
- Semaphore:
- 可以有多把鎖,允許多個線程同時擁有執行權
- 這些有執行權的線程如果並發訪問同一對象,會產生線程安全問題
- 線程是怎樣按照順序執行的?
- 線程執行過程中遇到異常會發生什么,怎樣處理?
- 寫出一個單例模式?
- 單例模式是最常遇到的設計模式之一,考查對經常碰到的問題的理解的深度
- 單例一共有5種實現方式:
- 餓漢
- 懶漢
- 靜態內部類
- 雙檢鎖
- 枚舉
- 要是寫了簡單的懶漢式可能會問: 要是多線程情況下怎樣保證線程安全呢?
- 使用雙檢鎖可以保證線程安全.
- 為什么要兩次校驗?光是雙檢鎖還會有什么問題?
- 對象在定義的時候加上volatile關鍵字
- 引申討論原子性和可見性,Java內存模型,類的加載過程
- 枚舉方式,靜態內部類,雙檢鎖都可以實現單例模式. 雙檢鎖的單例模式:
public Class Singleton { private Singleton() { } private volatile static Singleton instance; public static Singleton getInstance() { if (null == instance) { synchronized (Singleton.class) { if (null == instance) { instance = new Singleton(); } } } return instance; } }
- 雙檢鎖寫一個單例模式,為什么要使用volatile修飾對象?
- Object object = new Object();這里的object為null嗎?為什么?
- Object object = new Object();初始化的順序是什么?在JVM各個區域做了什么?
- 什么情況下會發生死鎖?寫一個死鎖?
- 死鎖的四個條件:
- 示例: 定義兩個ArrayList,都加上鎖A,B.線程1,2. 線程1獲取到鎖A,請求鎖B. 線程2獲取到鎖B,請求鎖A. 在等待對方釋放鎖的過程中都不會讓出已獲得的鎖
public class DeadLock { public static void main(String[] args) { final List<Integer> list1 = Arrays.asList(1, 2, 3); final List<Integer> list2 = Arrays.asList(4, 5 ,6); new Thread(new Runnable() { @Override public void run() { synchronized (list1) { for (Integer i : list1) { System.out.println(i); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (list2) { for (Integer i : list2) { System.out.println(i); } } } } }).start(); new Thread(new Runnable() { @Override public void run() { synchronized (list2) { for (Integer i : list2) { System.out.println(i); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (list1) { for (Integer i : list1) { System.out.println(i); } } } } }).start(); } }
- String a = "ab"; String b = "a" + "b"; a == b; 是否相等,為什么?
- 相等
- new一個對象賦給變量
- 這行表達式創建了幾個對象
- int a = 1; 是原子性操作嗎?
- 是
- 可以使用for循環直接刪除ArrayList的特定元素嗎?可能會出現什么問題?怎樣解決?
- 不可以使用for循環直接刪除ArrayList中的特定元素:
- 不同的for循環會發生不同的異常
- 泛型for會拋出ConcurrentModificationException
- 普通的for想要刪除集合中重復且連續的元素,只能刪除第一個
- 原因:
- JDK中的ArrayList源碼
- ArrayList中的remove有兩個同名方法,只是入參不同:
- 入參為Object的實現:
- 一般情況下程序的執行路徑走到else路徑下最終調用faseRemove() 方法,會執行System.arraycopy() 方法,導致刪除元素時涉及到數組元素的移動
- 普通for循環,在 遍歷第一個符合刪除條件的字符串時將該元素從數組中刪除,並且將后一個元素即第二個元素移動到當前位置,導致下一次遍歷時后一個字符串並沒有遍歷成功,所以無法刪除. 這種可以使用倒序刪除的方式來避免
- 解決方法: 使用迭代器Iterator
List<String> list = new ArrayList(Arrays.asList("a", "b", "b", "c", "d")); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String element = iterator.next(); if ("b".equals(element)) { iterator.remove(); } }
- 新的任務提交到線程池,線程池是怎么處理的?
- 第一步: 線程池判斷核心線程池里的線程是否都在執行任務. 如果不是,則創建一個新的工作線程來執行任務. 如果核心線程池里的線程都在執行任務,則執行第二步
- 第二步: 線程池判斷工作隊列是否已經滿了. 如果工作隊列沒有滿,則將新提交的任務存儲在這個工作隊列中等待. 如果工作隊列滿了,則執行第三步
- 第三步: 線程池判斷線程池的線程是否都處於工作狀態. 如果沒有,則創建一個新的工作線程來執行任務. 如果已經滿了,則交給飽和策略來處理這個任務
- AQS和CAS原理?
- 抽象隊列同步器AQS: AbstractQueuedSychronizer
- 如果說java.util.concurrent的基礎是CAS的話,那么AQS就是整個Java並發包的核心
- ReentrantLock, CountDownLatch, Semaphore都用到了AQS
- AQS實際上以雙向隊列的形式連接所有的Entry:
- ReentrantLock: 所有等待的線程都被放在一個Entry中並連成雙向隊列,前面一個線程使用ReentrantLock好了,則雙向隊列實際上的第一個Entry開始運行
- AQS定義了對雙向隊列所有的操作,並且只開放了tryLock和tryRelease方法給開發者使用.開發者可以根據自己的實現重寫tryLock和tryRelease方法來實現自己的並發功能
- 比較並替換CAS: Compare and Swap
- 假設有三個操作數:
- 內存之V
- 舊的預期值A
- 要修改的值B
- 當且僅當預期值A和內存值V相同時,才會將內存值修改為B並返回true. 否則什么都不做並返回false.
- 整個比較並替換的操作是一個原子操作
- CAS必須要volatile變量配合,這樣才能保證每次拿到的變量是主內存中最新的響應值. 否則舊的預期值A對某條線程來說,永遠是一個不會變的值A. 只要某次CAS操作失敗,則CAS操作永遠不會成功
- CAS高效地解決了原子操作的問題,但仍然存在三大問題:
- 循環時間長開銷很大
- 只能保證一個共享變量的原子操作
- ABA問題
- AtomicLong的底層實現原理?
- ReentrantLock是可重入鎖,什么是可重入鎖?
- CAS底層是怎么樣實現原子性的?
- synchronized底層實現原理?
- synchronized(this)原理:
- 兩條指令: monitorenter和monitorexit
- 同步方法: 從同步方法的反編譯的結果中可以看出 - 方法的同步並沒有通過指令monitorenter和monitorexit來實現,相對於普通方法,在常量池中多了ACC_SYNCHRONIZED標識符
- JVM就是根據ACC_SYNCHRONIZED標識符來實現方法同步的:
- 當方法被調用時,調用指令將會檢查方法的ACC_SYNCHRONIZED訪問標志是否被設置
- 如果設置了,執行線程將先獲取monitor,獲取成功之后才能執行方法體,方法執行完之后再釋放monitor
- 在方法執行期間,其余任何線程都無法再獲得同一個monitor對象
- lock和synchronized的區別?
- Java對象頭信息,偏向鎖,輕量鎖,重量級鎖及各自相互間的轉化?
- 修飾類的鎖和修飾方法的鎖的區別?
- volatile作用,指令重排相關?
- 理解volatile關鍵字的作用的前提是要理解Java的內存模型
- volatile關鍵字的作用主要有兩點:
- 多線程主要圍繞可見性和原子性兩個特性展開.使用volatile關鍵字修飾的變量,保證了在多線程之間的可見性.即每次讀取到volatile變量,一定是最新的數據
- 底層代碼的執行: Java代碼 -> 字節碼 -> 根據字節碼執行對應的C/C++代碼 -> C/C++代碼被編譯成匯編語言 -> 和硬件電路交互.現實中,為了獲取更好的性能,JVM可能會對指令進行重排序,多線程下可能會出現意想不到的問題.使用volatile則會禁止對語義重排序,不過也會一定程度上降低代碼的執行效率
- 從實踐角度而言,volatile的一個重要作用就是和CAS結合,保證了原子性. 比如AtomicInteger
Java線程池
- 線程池的工作原理以及核心參數
- 線程池的構造參數?
- 如何中斷一個線程,具體實現?正在running的線程能夠被中斷嗎?
- 線程中的線程個數,如何來設置?計算性的個數是CPU的個數還是CPU個數的2倍?
- CPU 100%怎樣定位?
數據結構
- B樹和B+樹是解決什么樣的問題的?怎么演化過來的?兩者之間的區別是什么?
- B樹和B+樹,既考查MySQL索引的實現原理,也考查數據結構基礎
- 首先從二叉樹說起:
- 因為會產生退化現象,提出平衡二叉樹
- 再提出怎么樣讓每一層放的節點多一些來減少遍歷高度,引申出m叉樹
- m叉搜索樹同樣會有退化現象,引出m叉平衡樹,即B樹
- 這個時候每個節點既放了key又放了value.怎樣使每個節點放盡可能多的key值,以減少遍歷高度也就是訪問磁盤的次數
- 可以將每個節點只放key值,將value值放在葉子節點,在葉子節點的value值增加指向相鄰節點的指針,這就是優化后的B+樹
- 然后談談數據庫索引失效的情況:
- 為什么給離散度低的字段,比如性別建立索引是不可取的?查詢數據反而更慢
- 如果將離散度高的字段和離散度低的字段,比如性別建立聯合索引會怎樣,有什么需要注意的?
Spring
- 看過哪些框架的源碼?
- 什么是Spring框架? Spring框架有哪些模塊?
- Spring中使用了哪些設計模式?
- Spring框架的好處?
- 什么是IOC控制反轉?
- 什么是依賴注入以及原理?
- Spring中@Autowired和@Resource的區別?
- BeanFactory和ApplicationContext有什么區別?
- Spring Bean的生命周期?
- Spring Bean的作用域有哪些以及各種作用域之間有什么區別?
- Spring框架中的單例Beans是線程安全的嗎?
- BeanFactory和FactoryBean的區別和應用場景?
- Spring的代理如何實現?
- JDK代理機制?
- AOP和IOC原理?
- AOP和IOC是Spring的精華部分
- AOP:
- AOP可以看作是對OOP的補充,對代碼進行橫向擴展
- 通過代理模式實現.代理模式有靜態代理和動態代理.
- Spring利用的是動態代理,在程序運行過程中將增強代碼織入源代碼中
- IOC: 控制反轉
- 將對象的控制權交給Spring框架,用戶使用對象無需創建,直接使用即可
- AOP和IOC重點要了解設計思想
- Spring怎樣解決循環依賴的問題
- Spring的循環依賴問題:
- 什么是循環依賴?
- 怎樣檢測出循環依賴?
- Spring循環依賴有幾種方式,使用基於setter屬性的循環依賴為什么不會出現問題?
- Bean的生命周期?
- dispatchServlet怎樣分發任務的?
- 1. 用戶發送請求 -> DispatcherServlet: 前端控制器收到請求后自己不進行處理,而是委托給其余解析器進行處理,作為統一的訪問點,進行全局的流程控制
- 2. DispatcherServlet -> HandlerMapping: HandlerMapping將會把請求映射為HandlerExecutionChain對象.HandlerExecutionChain包含一個Hander處理器,多個HandlerInterceptor攔截器
- 3. DispatcherServlet -> HandlerAdapter: HandlerAdapter將會將處理器包裝為適配器,從而支持多種類型的處理器
- 4. HandlerAdapter -> 處理器功能方法的調用: HandlerAdapter將會根據適配的結果調用真正的處理器的功能處理方法,完成功能處理,並返回一個ModelAndView對象. ModelAndView對象包含模型數據.邏輯視圖名
- 5. ModelAndView的邏輯視圖名 -> ViewResolver: ViewResolver將邏輯的視圖名解析為具體的View
- 6. View -> 渲染: View會根據傳進來的Model模型數據進行渲染,這里的Model是一個Map數據結構
- 7. 返回控制權給DispatcherServlet,由DispatcherServlet返回響應給用戶
數據庫
- 數據庫中的范式有哪些?
- 第一范式: 數據庫中的表的所有字段值都是不可分割的原子數據項
- 第二范式: 數據庫表中的每一列都和主鍵相關,而不能只和主鍵的某一部分相關
- 第三范式: 數據庫表中每一列數據都和主鍵直接相關,不能間接相關
- 范式是為了減少數據冗余
- MySQL給離散度低的字段建立索引會出現什么問題?
- 重復性強的字段,不適合添加索引
- MySQL給離散度低的字段,比如性別設置索引,再以性別作為條件查詢反而會更慢
- 一個表可能會涉及兩個數據結構:
- 數據表: 存放表中的數據
- 索引
- 索引:
- 將一個或幾個字段(組合索引)按規律排列起來,再附加上該字段所在行數據的物理地址(位於表中)
- 比如有個字段是年齡,如果需要選取某個年齡段的所有行,那么一般情況下可能需要進行一次全表掃描
- 但是如果以這個年齡段建立一個索引,那么索引會按照年齡值根據特定的數據結構建一個排列,這樣在索引中就能迅速定位,不需要進行全表掃描
- 為什么性別不適合建立索引呢?
- 因為訪問索引需要有額外的IO開銷,從索引中拿到的只是地址,要想真正訪問到數據還是要對表進行一次IO
- 如果要從表中的100萬行數據中取幾個數據,那么利用索引迅速定位,訪問索引的IO開銷就可以忽略不計
- 如果要從標中的100萬行數據取50萬行數據,再訪問50萬次表,加起來的開銷並不會比對表進行一次完整的掃描小
- 如果將性別字段設為聚焦索引,那么肯定能加快大約一半該字段的查詢速度
- 聚焦索引:
- 指的是表本身數據按照哪個字段的值來進行排序
- 聚焦索引不會付出額外IO開銷
- 聚焦索引只能有一個
-因此聚焦索引要用到搜索最頻繁的字段上- 可以根據業務場景需要,將性別和其余的字段建立聯合索引. 比如時間戳,要將時間戳字段放在性別前面
- MySQL的最左匹配原則?
- MySQL的隔離級別?
- B+,B樹的區別?
- MySQL的常見優化和注意事項?
- 數據庫慢查詢優化思路
- MySQL中的log有哪些?分別有什么作用?
- undo log:
- redo log:
- binlog:
- 數據庫ACID?
- 數據庫事務的隔離級別?
- 數據庫的分庫分表?
- 分庫分表的全局唯一ID如何實現?
- 數據庫中的索引的結構?什么情況下適合建索引?
- 數據庫中的索引的結構是一種排序的數據結構,數據庫的索引是通過B樹和變形的B+樹實現的
- 什么情況下不適合建立索引:
- 對於在查詢過程中很少使用或者參考的列
- 對於只有很少數據值的列
- 對於定義為image,text和bit數據類型的列
- 當修改性能遠遠大於檢索性能時
- 根據系統自身的環境情況,有效限制線程數量,使得運行效果達到最佳
- 線程主要是通過控制執行線程的數量,超出數量的線程排隊等候,等待有任務執行完畢,再從隊列最前面取出任務執行
- MySQL常用優化?
- SQL優化
- 表結構優化
- 索引優化
- 緩存參數優化
- Redis的數據類型有哪些?
分布式
- CAP理論?
- 寫一個生產者消費者模式?
- 生產者消費者模式:
- synchronized鎖住一個LinkedList:
- 生產者: 只要隊列不滿,生產后往里存
- 消費者: 只要隊列不空,消費后往外取
- 兩者通過wait() 和notify() 進行協調
- 要考慮怎么樣提高效率
- 熟悉消息隊列設計精要思想及使用
- 為什么要使用消息隊列MQ?
- 異步處理: 相對於傳統的串行,並行方式,提高了系統的吞吐量
- 應用解耦: 系統間通過消息通信,不用關心其他系統的處理
- 流量削峰: 可以通過消息隊列長度控制請求量,可以緩解短時間內高並發請求
- 日志處理: 解決大量日志傳輸
- 消息通訊: 消息隊列一般都內置了高效的通信機制,因此可以用在純的消息通訊. 比如實現點對點消息隊列,聊天室等
- 如何保證消息隊列MQ的高可用?
- 將所有Broker和待分配的Partition排序
- 將第i個Partion分配到第 (i mod n) 個Broker上
- 將第i個Partion的第j個Replica分配到第 ((i+j) mod n) 個Broker上
- MQ有哪些常見問題?如何解決這些問題?
- 消息隊列的順序問題
![]()
- 消息有序指的是可以按照消息的發送順序來消費
- 假定生產者產生了2條消息:M1,M2.假定M1發送到S1,M2發送到S2.如果要保證M1優先於M2被消費,如何保證:
- 解決方案:
- 保證生產者 - MQSever - 消費者是一對一對一的關系
- 缺陷:
- 並行度會成為系統的瓶頸,吞吐量不夠
- 會出現更多的異常處理問題: 只要消費者出現問題,就會導致整個流程堵塞,不得不解決阻塞的問題
- 可以通過合理的設計或者將問題分解來規避:
- 不關注亂序的應用實際大量存在
- 隊列無序並不意味着消息無序
- 消息的重復問題:
- 造成消息重復的根本原因: 網絡不可達
- 所以解決這個問題的方法就是繞過這個問題.也就是: 如果消費端收到兩條一樣的消息,應該怎樣處理?
- 解決方案:
- 消費端處理消息的業務邏輯保持冪等性
- 只要保持冪等性,不管來多少條重復消息,最后處理的結果都一樣
- 保證每條消息都有唯一編號且保證消息處理成功與去重表的日志同時出現
- 利用一張日志表來記錄已經處理成功的消息的ID,如果新到的消息ID已經在日志表中,那么就不再處理這條消息
- Kafka,ActiveMQ,RabbitMQ,RocketMQ各有什么優缺點?
- Dubbo是什么?主要應用場景?核心功能?
- Dubbo的實現過程?
- Dubbo節點角色?
- Dubbo中的調用關系?
- 服務容器負責啟動,加載,運行服務提供者
- 服務提供者在啟動時,向注冊中心注冊自己提供的服務
- 服務消費者在啟動時,向注冊中心訂閱自己所需的服務
- 注冊中心返回服務提供者地址列表給消費者,如果有變更,注冊中心將基於長連接推送變更數據給消費者
- 服務消費者,從提供者地址列表中,基於軟負載均衡算法,選擇一台提供者進行調用.如果調用失敗,再選擇另一台進行調用
- 服務消費者和服務提供者,在內存中累計調用次數和調用時間,定時每分鍾發送統計數據到監控中心
- Dubbo的注冊中心集群宕機,發布者和訂閱者之間還能夠通信嗎?
- Dubbo支持哪些協議,每種協議的應用場景,優缺點?
- Dubbo的超時時間怎樣設置?
- Dubbo的負載均衡策略有哪些?
- Random:
- 隨機負載均衡策略,按權重設置隨機概率
- 在一個截面上的碰撞概率高,但調用量越大分布越均勻,而且按概率使用權重后也比較均勻,有利於動態調整提供者權重
- RoundRobin:
- 輪循負載均衡策略,按公約后的權重設置輪循比率
- 存在慢的提供者累積請求的問題
- 比如: 第二台機器很慢,但沒有宕機,當請求到第二台機器就會卡住,久而久之,所有的請求都會卡在 調到第二台機器的時候
- LeastActive:
- 最少活躍調用數負載均衡策略,相同活躍數的隨機調用.活躍數指的是調用前后計數差
- 使慢的提供者收到更少的請求,因為越慢的提供者的調用前后計數差會越大
- ConsistentHash:
- 一致性Hash負載均衡策略,相同的參數請求總是發到同一提供者
- 當某台提供者宕機時,原本發往該提供者的請求,基於虛擬節點,平攤到其他提供者,不會引起劇烈變動
- 缺省只對第一個參數Hash,如果要修改,需要修改 < dubbo:parameter key="hash.arguments" value="0,1" />
- 缺省使用160份虛擬節點,如果要修改,需要修改< dubbo:parameter key="hash.nodes" value="320" >
- Dubbo集群容錯策略?
- Failover: 失敗自動切換,當出現失敗,重試其他服務器. 通常用於讀操作,但重試會帶來更長延遲. 可以通過設置retries="2" 來設置重試次數,不包含第一次
- Failfast: 快速失敗,只發起一次調用,失敗立即報錯. 通常用於非冪等性的寫操作,比如新增記錄
- Failsafe: 失敗安全,出現異常時,直接忽略. 通常用於寫入審計日志等操作
- Failback: 失敗自動恢復,后台記錄失敗請求,定時重發. 通常用於消息通知操作
- Forking: 並行調用多個服務器,只要一個成功即返回. 通常用於實時性要求比較高的讀操作,但需要浪費更多服務資源,可以通過設置 forks="2"來設置最大並行數
- Broadcast: 廣播調用所有提供者,逐個調用,任意一台報錯即報錯. 通常用於通知所有提供者更新緩存或日志等本地資源信息
- Dubbo的動態代理策略?
- Dubbo作為RPC框架,首先要完成的就是跨系統,跨網絡的服務調用
- 消費方和提供方遵循統一的接口定義
- 消費方調用接口時,Dubbo將其轉換為統一格式的數據結構
- 通過網絡傳輸,提供方根據規則找到接口實現,通過反射完成調用
- 消費方獲取的是對遠程服務的一個代理 Proxy, 提供方因為要支持不同的接口實現,需要一個包裝層Wrapper
- 調用過程:
- 消費方的Proxy和提供方的Wrapper得以讓Dubbo構建出復雜,統一的體系
- 這種動態代理與包裝是通過SPI的插件方式實現的,接口就是ProxyFactory:
@SPI("javassist") public interface ProxyFactory { @Adaptive({Constants.PROXY_KEY}) <T> T getProxy(Invoker<T> invoker) throws RpcException; @Adaptive({Constants.PROXY_KEY}) <T> invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException; }
- ProxyFactor有兩種實現方式:
- 基於JDK的代理實現
- 基於javassist的實現
- ProxyFactory接口上定義了 @SPI("javassist"), 默認為javassist的實現
- Dubbo有哪些注冊中心?
- Dubbo與Spring之間的關系?
- Dubbo使用的是什么通信框架?
- Dubbo的安全機制是如何實現的?
- Dubbo連接注冊中心和直連的區別?
- Dubbo的通信協議dubbo協議為什么采用異步單一長連接的方式?
- Dubbo的通信協議dubbo協議適用范圍和應用場景?
- Dubbo支持哪些序列化協議?Hessian?Hessian的數據結構?
- Dubbo序列化: 阿里基於Java的序列化實現
- Hessian2序列化: Hessian是一種跨語言的高效二進制的序列化方式. 這里實際不是原生的Hessian2序列化,而是阿里修改過的Hessian Lite,是Dubbo默認啟用的序列化方式
- Json序列化: 目前有兩種實現:
- 采用阿里的fastjson庫
- 采用Dubbo自身實現的簡單Json庫
- 一般情況下,json這種文本序列化性能不如二進制序列化
- Kryo和FST: Kryo和FST的性能普遍優於Hessian和Dubbo序列化
- Hessian序列化和Java默認的序列化區別?
- Hessian是一個輕量級的remoting on http工具,采用Binary RPC協議,很適合發送二進制數據,同時又具有防火牆穿透能力
- Hessian支持跨語言串行
- Hessian序列化比Java默認的序列化具有更好的性能和易用性
- Hessian序列化支持的語言比較多
- Protoco Buffer是什么?
- Protoco Buffer是谷歌出品的一種輕量並且高效的結構化數據存儲格式,性能比Json,XML強大得多
- Protoco的序列化和反序列化簡單並且速度快. 原因在於:
- 編碼和解碼方式簡單,只需要簡單的數學運算=位移等等
- 采用Protoco Buffer自身的框架代碼和編譯器共同完成
- Protoco Buffer的數據壓縮效果好,即序列化后數據量的體積小. 原因在於:
- 采用獨特的編碼方式,比如Varint,Zigzag編碼方式等等
- 采用 T - L - V 數據存儲方式,減少了分隔符的使用並且數據存儲得緊湊
- 注冊中心宕機了可以繼續通信嗎?
- 可以
- Dubbo消費者在應用啟動時會從注冊中心拉取已注冊的生產者的地址接口,並緩存在本地. 每次調用時,按照本地存儲的地址進行調用
- ZooKeeper有什么用?ZooKeeper原理是什么?
- ZooKeeper是一個分布式應用協調系統,已經應用到了許多分布式項目中,用來完成
- 統一命名服務
- 狀態同步服務
- 集群管理
- 分布式應用配置項的管理
- 每個Server在內存中存儲了一份數據
- ZooKeeper啟動時,將從實例中選舉一個leader(Paxo協議)
- Leader負責處理數據更新等操作(Zab協議)
- 當且僅當大多數Server在內存中成功修改數據時,一個更新操作成功
- Netty有什么用?NIO,BIO,AIO有什么用?有什么區別?
- Netty是一個網絡通信框架
- Netty進行事件處理的流程:
- Channel是連接的通道,是ChannelEvent的產生者
- ChannelPipeline可以理解為ChannelHandler的集合
- IO的方式通常分為:
- 同步阻塞的BIO
- 同步非阻塞的NIO
- 異步非阻塞的AIO
- 在使用同步阻塞的BIO的網絡應用:
- 如果要同時處理多個客戶端請求,或者是在客戶端要同時和多個服務器進行通訊,就必須使用多線程來處理
- 同步非阻塞的NIO基於Reactor:
- 當socket有流可讀或者可寫入socket時,操作系統會相應的通知引用程序進行處理,應用再將流讀取到緩沖區或者寫入操作系統
- 這個時候,不是一個連接就要對應一個處理線程了.而是有效的請求,對應一個線程,當連接沒有數據時,是沒有工作線程來處理的
- 異步非阻塞的AIO與NIO不同:
- 當進行讀寫操作時,只需要直接調用API的read或者write方法即可
- 這兩種方法均為異步的:
- 對於讀操作而言, 當有流可讀取時,操作系統會將可讀的流傳入read方法的緩沖區,並通知應用程序
- 對於寫操作而言,當操作系統將write方法傳遞的流寫入完畢時,操作系統主動通知應用程序
- read或者write方法都是異步的,完成后會主動調用回調函數
- 為什么要進行系統拆分?拆分不用Dubbo可以嗎?
- 系統拆分的分類:
- 從資源角度:
- 應用拆分
- 數據庫拆分
- 從采用的先后順序:
- 水平擴展
- 垂直拆分
- 業務拆分
- 水平拆分
- 是否使用Dubbo依據實際業務場景來決定:
- 當垂直應用越來越多,應用之間的交互不可避免,將核心業務抽取出來,作為獨立的服務,逐漸形成穩定的服務中心,使前端應用能更快速的響應多變的市場需求. 此時,用於提高業務復用以及整合的分布式框架RPC是關鍵
- 當服務越來越多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時需要增加一個調度中心基於訪問壓力實時管理集群容量,提高集群的利用率. 此時,用於提高機器利用率的資源調度和治理中心SOA是關鍵
- Dubbo和Thrift有什么區別?
- Dubbo支持服務治理,而Thrift不支持
- Thrift是跨語言RPC框架
- Redis如何實現分布式鎖?
setNX key value
value保證唯一性,避免線程A釋放線程B拿到的鎖
- 實現分布式鎖如果使用setNX命令,那么如果鎖的機器宕機了,其他服務怎么拿得到鎖?
設置過期時間
- 如何來設置過期時間?先 set key value,再設置過期時間嗎?如果是兩條命令,set key value成功,設置過期時間失敗,一樣存在如上問題.那么如何來保證set key value和設置過期時間的原子操作?
set命令提供了相應的原子操作命令來保證set key value和設置過期時間的原子操作
- Redis是使用的集群嗎?如果是集群,當客戶端執行setNX時Redis集群,如何做才認為set成功?一半集群set成功,就認為成功嗎?還是全部set成功才認為成功?
Redis集群使用的是多主多從,當一半以上的主節點set成功,才算成功
- 一半成功就算成功,假設Redis集群有a,b,c三個主節點,各有一個從節點,線程A在a,b主節點set成功,而在c主節點set失敗,此時線程A獲取到鎖,而此時剛好b主節點宕機,剛好數據還沒有同步到其從節點,那么此時從節點b'升級到主節點,那么線程B對相同的key執行set命令來獲取鎖,在b'和c節點set成功,這樣同樣可以獲取到鎖,這個時候出現了多個線程獲取到同一把鎖?
- Redis緩存,如何完成更新?
先Delete緩存,再更新DB,延時一段時間再Delete緩存
或者先更新DB,延時一段時間再Delete緩存
- 為什么要延時一段時間?
因為如果線程A先Delete緩存,此時線程B發現緩存中沒有數據,則從DB中讀出老數據並reload到緩存,線程A更新數據庫之后,則緩存與數據庫數據庫中的數據不一致,因此需要延時一段時間執行刪除
- 如果Delete失敗?
重試機制
- Redis中的數據結構有哪些?跳表用於哪些場景?
- volatile關鍵字?Lock?
- 並發編程中的問題:
- 原子性問題
- 可見性問題
- 有序性問題
- volatile:
- volatile關鍵字能保證可見性,只能禁止指令重排序,不能保證原子性
- 可見性只能保證每次讀取的是最新的值,但是volatile無法保證對變量的操作的原子性
- 在生成的會變語句中加入Lock關鍵字和內存屏障
- Lock:
- Lock實現提供了比使用synchronized方法和語句可獲得的更廣泛的鎖定操作,能夠使用更優雅的方式解決線程同步問題
- 用synchronized修飾的方法或者語句塊在代碼執行完之后鎖自動釋放,然而使用Lock修飾的方法或者語句需要手動釋放鎖
- Java每次修改都需要重新編譯打包部署,有沒有更好的額方法?
熱部署
- 進程間通信有哪幾種方式?
- 管道: Pipe
- 命名管道: Named Pipe
- 信號: Signal
- 消息隊列: Message
- 共享內存
- 內存映射: Mapped Memory
- 信號量: Semaphore
- 套接口: Socket
- Synchronized修飾的方法實例?
Synchronized修飾靜態方法,鎖定本身不是實例.非靜態方法鎖定實例
- 操作系統什么情況下會死鎖?
- 死鎖: 指多個進程在運行過程中因爭奪資源而造成的一種僵局
- 產生原因: 競爭資源
- 當系統中多個進程使用共享資源,並且資源不足以滿足需要,會引起進程對資源的競爭而產生死鎖
- 進程間推進的順序不當
- 請求和釋放資源的順序不當,同樣也會產生進程死鎖
- 產生死鎖的四個條件
- 互斥條件: 進程獨占資源
- 請求與保持: 進程因請求資源而阻塞時,對已獲得的資源保持不放
- 不剝奪條件: 進程已經獲得資源,在未使用完之前,不能強行剝奪
- 循環等待: 若干進程之間形成頭尾相接的循環等待資源關系
- 如何理解分布式鎖?
線上服務器是分布式多台部署的,經常會面臨解決分布式場景下數據一致性問題,這是就要利用分布式鎖來解決這些問題
- 分布式事務有哪些實現方式?
- 微服務的架構設計?
JVM
- Java類的初始化順序?
- Java類的初始化順序:
- 基類靜態代碼塊,基類靜態成員變量. 並列優先級,按照代碼中出現的先后順序執行,並且只有第一次加載時執行
- 派生類靜態代碼塊,派生類靜態成員變量. 並列優先級,按照代碼中出現的先后順序執行,並且只有第一次加載時執行
- 基類普通代碼塊,基類普通成員變量. 並列優先級,按照代碼塊中出現的先后順序執行
- 基類構造函數.
- 派生類普通代碼塊,派生類普通成員變量. 並列優先級,按照代碼塊中出現的先后順序執行
- 派生類構造函數.
- 為什么要進行分代?
- 對方法區和永久區的理解以及兩者的區別?
- 方法區是JVM規范中要求的 ,永久區是Hotspot虛擬機對方法區的具體實現
- 方法區是規范,永久區是實現方式(JDK 1.8以后做了改變)
- 一個Java類有3個文件,編譯后有幾個class文件?
- 文件中有幾個類,編譯后就有幾個class文件
- 局部變量使用前需要顯式的賦值,否則編譯通過不了,為什么要這樣設計?
- 成員變量是可以不經初始化的,在類加載過程的准備階段即可以給成員變量賦予默認值.
- 局部變量在使用之前需要顯式賦予初始值
- javac不是推斷不出不可以這樣做,對於成員變量而言,其賦值和取值訪問的先后順序具有不確定性,對於一個成員變量可以在一個方法調用前賦值,也可以在方法調用后進行賦值,這是運行時發生的,編譯器確定不了,交給JVM做比較合適
- 對於局部變量而言,局部變量的賦值和訪問順序是確定的,這樣設計是一種約束,盡最大程度減少使用者犯錯的可能性:
- 假使局部變量可以使用默認值,可能總會無意間忘記賦值,進而導致不可預期的情況出現
- JVM的內存分區?
- 堆內存的分區以及每個分區的垃圾回收算法?回收器G1,CMS有標記清除,標記整理法?
- 如何排查Full GC,OOM?
- 線程個數太多會導致OOM,但是這里的線程包括程序的所有線程嗎?比如包括pigeon的線程池嗎?
- JVM中類的加載過程,雙親委派模型中有哪些方法?
- 類加載過程:
- 加載
- 驗證: 驗證階段作用是保證Class文件的字節流包含的信息符合JVM規范,不會給JVM造成傷害
- 准備: 准備階段為變量分配內存並設置類變量的初始化
- 解析: 解析過程是將常量池內的符號引用替換成直接引用
- 初始化
- 雙親委派模型中的方法: 雙親委派是指如果一個類收到類加載請求,不會自己先嘗試加載,先找父類加載器完成.當頂層啟動類加載器表示無法加載這個類的時候,子類才會自己去加載.當回到最開始的發起者加載器還無法加載時,並不會向下找,而是拋出ClassNotFound異常
- 啟動(Bootstrap)類加載器
- 標准擴展(Extension)類加載器
- 應用程序(Application)類加載器
- 上下文(Custom)類加載器
- 意義是防止內存中出現多份同樣的字節碼
- 垃圾收集器了解哪些?
- GC ROOTS包括哪些?
- GC算法?什么樣的對象算是可回收對象?可達性分析?CMS收集器?
- JVM如何判斷一個對象已經變成可回收的垃圾:
- 引用計數器法: 引用計數器無法解決循環引用的問題
- 根搜索算法: 從一系列的GC Roots對象開始向下搜索,搜索的路徑稱為引用鏈.當一個對象到GC Roots之間沒有引用鏈時稱為引用不可達.引用不可達的對象被認為是可回收對象
- 幾種垃圾回收器:
- Serial New或者Serial Old: 串行
- Parrallel New: 並行
- Parrallel Scavenge
- Parrallel Old
- G1: 一款並行與並發收集器,並且可建立可預測的停頓時間模型,整體上是基於標記清理,局部采用復制
- CMS
- CMS收集器是一個以獲得最短回收停頓時間為目標的收集器,是一種並發收集器,采用的是Mark - Sweep算法
- JVM中的GC復制算法是怎樣實現的?
- JVM分為哪些區?每一個區的作用?
- 方法區(Method): 被所有線程共享,方法區包含所有的類信息和靜態變量
- 堆(Heap): 被所有的線程共享,存放對象實例以及數組,Java堆是GC的主要區域
- 棧(Stack): 每一個線程包含一棧區,棧中保存一些局部變量
- 程序計數器: 當前線程執行的字節碼行指示器
- JVM新生代,老年代,持久代.都存儲些什么內容?
- 新生代存放所有新生成的對象
- 老年代存放的都是一些生命周期較長的對象
- 持久代主要存放的是Java類的類信息,與垃圾收集要收集的Java對象關系不大
- 內存溢出和內存泄露?
- 內存溢出: out of memory,程序申請內存時,沒有足夠的內存
- 內存泄露: 垃圾對象無法回收,可是使用memory analyzer工具查看泄露
- 進程與線程?
- 進程: 運行中的程序,具有獨立性,動態性,並發性
- 線程: 指進程中的順序執行流
- 進程與線程的區別:
- 進程間不共享內存
- 創建進程進行資源分配的代價要大得多,所以多線程在高並發的環境中效率高
- 序列化和反序列化?
- 序列化: 將Java對象轉化為字節序列
- 反序列化: 將字節序列轉化為Java對象
- 序列化和反序列化主要是為了Java線程間的通訊,實現對象傳遞.只有實現了Serializable或者Externalizable接口類對象才可被序列化
- 64位JVM中,int的長度是多長?
在JVM中,int類型的變量的長度是一個固定值,與平台無關,4個字節,長度為32位
- Java中WeakReference與SoftReference的區別?
- Java中一共有四種類型的引用:
- StrongReference
- SoftReference
- WeakReference
- PhantomReference
- StrongReference是Java的默認引用實現,會盡可能長時間的存活於JVM內,當沒有任何對象指向時將會被GC回收
- SoftReference會盡可能長的保留引用直到JVM內存不足時才會被回收,通過虛擬機保證.這一特性使得SofeReference非常適合緩存應用
- WeakReference是一個弱引用,當所引用的對象在JVM內不再有強引用時,將被GC回收
- WeakReference和SoftReference的區別:
- WeakReference與SoftReference都有利於提高GC和內存的效率
- WeakReference一旦失去最后一個強引用,就會被GC回收
- SoftReference會盡可能長的保留引用直到JVM內存不足時才會被回收,通過虛擬機保證
- JVM內存的划分?
- Java堆的划分?
- Java中堆和棧有什么區別?
- Java中的堆和棧屬於不同的內存區域,使用目的也不同
- 棧通常用於保存方法幀和局部變量.而對象總是在堆上分配
- 棧通常比堆小,也不會在多個線程之間共享,而堆是被整個JVM所有線程共享
- Java堆空間及GC?
- Java堆空間:
- 當通過Java命令啟動Java進程的時候,會分配內存,內存的一部分用於創建堆空間
- 當程序中創建對象的時候,就從堆空間中分配內存
- GC:
- GC是JVM內部的一個進程,回收無效對象的內存用於將來的分配
- 哪些對象會被JVM回收?
網絡
- TCP如何保證可靠性傳輸?三次握手過程?
- 在TCP連接中,數據流必須以正確的順序送達對方
-TCP可靠性:
- 通過順序編碼和確認(ACK) 來實現的
- TCP連接是通過三次握手進行初始化的,三次握手的目的是同步連接雙方序列號和確認號並交換TCP窗口大小信息:
- 第一次: 客戶端發起連接
- 第二次: 表示服務器收到了客戶端請求
- 第三次: 表示客戶端收到了服務器反饋
- 如何識別session?
- 在cookie中存儲的session id
- HTTP報文結構?
- HTTP狀態碼?
- Linux下的常用命令:
- cd: 用來改變所在目錄. cd / - 轉到根目錄, cd ~ - 轉到用戶目錄
- ls: 用來查看目錄的內容
- cp: 用來拷貝文件. cp sourceFileName targetFileName
- mv: 移動文件. mv t.txt Document
- 常用的Hash算法有哪些?
- 加法Hash: 所謂的加法Hash就是把輸入元素一個一個加起來構成最后的結果
- 位運算Hash: 這種類型的Hash函數通過利用各種位運算,比如移位或者異或來充分的混合輸入元素
- 乘法Hash: 33*hash + key.charAt(i)
- 什么是一致性Hash?
- 一致性Hash的設計目標是為了解決因特網中的熱點(Hot spot)問題,一致性Hash算法提出了在動態變化的Cache環境中,判定Hash算法好壞的四個定義:
- 平衡性 :Balance
- 單調性 :Monotonicity
- 分散性 :Spread
- 負載 :Load
- 表單提交中,get和post的區別?
- get是從服務器獲取信息, post是向服務器傳信息
- get傳送的數據量比較小, post傳遞的數據量可以比較大
- get的安全性比post低
- TCP協議和UDP協議有什么區別?
- TCP: Tranfer Control Protocol, 是一種面向連接的保證傳輸協議,在傳輸數據流之前,雙方會建立一條虛擬的通信道,可以極少差錯傳輸數據
- UDP: User DataGram Protocol,是一種無連接的協議,使用UDP時,每一個數據段都是一個獨立的信息,包括完整的源地址和目的地,在網絡上以任何可能的路徑到達目的地.因此,能否到達目的地,以及到達目的地的時間和內容的完整性都不能保證
- TCP比UDP多了建立連接的時間.相對UDP而言,TCP具有更高的安全性和可靠性
- TCP協議傳輸的大小不限制,一旦連接被建立,雙方可以按照吧一定的格式傳輸大量的數據,而UDP是一個不可靠協議,大小有限制,每次不能超過64K
- Java IO模型有哪幾種?
- 同步和異步,阻塞和非阻塞的區別?
- Netty基本介紹