Java 面試總結
基礎一
JVM-內存區域分配
所有線程的共享數據區:
方法區:存儲每一個類的結構信息
堆區:最大 大部分類實例、對象、數組
每一個線程的一塊私有數據區:
虛擬機棧:存儲局部變量、操作數棧、動態鏈接、方法返回地址
本地方法棧:功能與虛擬機棧類似,爲native方法服務
pc寄存器:存放當前正在執行的字節碼指令的地址
JVM-類加載機制
1加載-2驗證-3準備(內存初始化)-4解析(將常量池的符號引用替換爲直接引用,符號引用是用一組符號來描述所引用的目標,直接引用是指向目標的指針)-5初始化(執行類構造器、類變量賦值、靜態語句塊)
JVM-內存分配(堆上的內存分配)
-
新生代:
進入條件:優先選擇在新生代的Eden區被分配 -
老年代:
進入條件:
大對象經過第一次MinorGC仍存在,能被Survivor容納
如果Survivor中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於等於該年齡的對象進入老年代
如果Survivor空間無法容納新生代中Minor GC之後還存活的對象
JVM-GC回收機制
回收對象:通過一系列的GC Roots的對象作爲起點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時則此對象是不可用的。
如何回收:新生代使用複製算法,老年代使用標記-清理或者標記-整理
複製算法:將可用內存按容量劃分爲Eden、from survivor、to survivor,分配的時候使用Eden和一個survivor,Minor GC後將存活的對象複製到另一個survivor,然後將原來已使用的內存一次清理掉。這樣沒有內存碎片。
標記-清除:首先標記出所有需要回收的對象,標記完成後統一回收被標記的對象。會產生大量碎片,導致無法分配大對象從而導致頻繁GC。
標記-整理:首先標記出所有需要回收的對象,讓所有存活的對象向一端移動。
MinorDC發起條件:當Eden區不足以繼續分配對象
Full GC條件:
1、調用System.gc
2、老年代空間不足
3、方法區空間不足
JVM-垃圾收集器
串行收集器:一個線程,慢 並行收集器:可控的吞吐量,最大停頓時間(複製,標記-整理)
併發收集器:以最短停頓時間爲目標 精確控制停頓時間且垃圾回收效率最高
CMS針對老年代,有初始標記、併發標記、重新標記、併發清除四個過程,標記階段會Stop The World,使用標記-清除算法,所以會產生內存碎片。
基礎二
容器
ArrayList:數組 增刪慢查詢快 自動擴容 1.5倍
LinkedList:雙向鏈表 增刪快查詢慢
HashMap :1.8以前數組+鏈表 1.8之後紅黑樹 多線程Put操作時會出現覆蓋
容器默認大小爲16 負載因子爲0.75 當size>16*0.75時發生擴容
CurrentHashMap:HashTable 在每次同步執行時都要鎖住整個結構。ConcurrentHashMap 鎖的方式是稍微細粒度的。ConcurrentHashMap 將 hash 表分爲 16 個桶(默認值)
ConcurrentHashMap 最大併發個數爲桶數,加鎖的時候就是鎖住故障segment
ConcurrentHashMap包含若干個Segment對象組成的數組 某個Segment對象守護整個散列表的若干個桶 每一個桶是由若干個HashEntry對象連接起來的鏈表
多線程
狀態:新建-就緒-運行-阻塞-就緒-運行-死亡
實現:Thread Runnable Callable
線程池的作用:線程創建和銷燬的開銷大,放回線程池有效利用
線程間通信的方式:
- 等待通知機制 wait() notify() join() interrupted()
- 併發工具: synchronized lock CountDownLatch semaphore CyclicBarrier
鎖
定義: 不同線程資源競爭下分配不同線程執行方式的同步工具 獲得鎖才能訪問同步代碼
synchronized
- wait:釋放佔有的對象鎖釋放CPU,進入等待隊列,只能通過notify/all繼續該進程
- sleep:釋放CPU,不釋放佔有的對象鎖,可以在sleep結束後自動繼續該進程
- notify: 喚醒等待隊列中的一個線程,使其獲得鎖進行訪問
- notifyall::喚醒等待隊列中等待該對象鎖的全部線程,讓其競爭去獲得鎖
lock
與synchronized相同的語義 必須手動釋放鎖
性能:資源競爭激烈情況下,lock性能比synchronized好
用法:synchronized可以用在代碼塊,方法上。lock通過代碼實現,需手動釋放
原理:synchronized monitorenter monitorexit lock 使用AQS在代碼級別實現
volatile
直接與主內存交互,讀寫保證可見性
禁止JVM進行指令重排序
ThreadLocal:
new ThreadLocal 讓每一個線程內部維護一個ThreadLocalMap ,每次存取都會先獲取當前線程Id,然後得到該線程對象中的Map。
線程池
核心參數
- corePoolSize:核心線程數量
- maximumPoolSize: 線程池允許的最大線程數
- workQueue:阻塞隊列 存儲等待執行的任務
- keepAliveTime:線程沒有任務執行時可以保持的時間
- unit:時間單位
- threadFactory:線程工廠,創建線程
- rejectHandler:拒絕任務提交時的策略(拋異常、用調用者所在的線程執行任務、丟棄隊列中第一個任務執行當前任務、直接丟棄任務)
ThreadPoolExcutor.execute
- 如果運行的線程數 < corePoolSize,直接創建新線程,即使有其他線程是空閒的
- 如果運行的線程數 >= corePoolSize
- 如果插入隊列成功,則完成本次任務提交,但不創建新線程
- 如果插入隊列失敗,說明隊列滿了
- 如果當前線程數 < maximumPoolSize,創建新的線程放到線程池中
- 如果當前線程數 >= maximumPoolSize,會執行指定的拒絕策略
信號量Semaphore:阻塞線程且能控制統一時間請求的併發量的工具
CyclicBarrier:可以讓一組線程相互等待,當每個線程都準備好之後,所有線程才繼續執行的工具類
IO
BIO:InputStream OutputStream Reader Writer 同步阻塞模型
NIO:Channel Buffer Selector IO多路複用的同步非阻塞模型
同步非阻塞:進程先將一個套接字在內核中設置成非阻塞再等待數據準備好,在這個過程中反覆輪詢內核數據是否準備好,準備好之後最後處理數據返回
AIO:屬於事件和回調機制的異步非阻塞模型
Web框架和數據庫
Spring
Spring是個包含一系列功能的合集,如快速開發的Spring Boot,支持微服務的Spring Cloud,支持認證與鑑權的Spring Security,Web框架Spring MVC。IOC與AOP依然是核心。
MVC的流程
1、發送請求 DispatcherServlet攔截器拿到交給HandlerMapping
2、依次調用配置的攔截器,最後找到配置好的業務代碼Handler並執行業務方法
3、包裝成ModelAndView返回給ViewResolver解析器渲染頁面
Bean的生命週期
Bean實例化-值和bean的引用注入到Bean對應的屬性-把容器信息注入BeanBe-Bean處理-Bean初始化和銷燬
Bean的作用域
- Singleton:始終指向同一對象
- prototype:原型模式每次通過Spring容器獲取bean時,容器都創建一個新的實例
- request:在一次Http請求中,容器會返回該Bean的同一實例。不同的Http請求產生新的Bean,該bean僅在當前HttpRequest內有效。
- session:在一次HttpSession中,容器會返回該Bean的同一實例。不同的Session請求創建新的實例,該bean實例僅在當前Session內有效。
- global Session:一個全局的Http Session中,容器會返回該Bean的同一個實例,僅在使用portlet context時有效。
IOC(重點)
控制反轉:原先是自己主動創建,現在是容器工具創建實例, 面向接口編程和配置文件減少對象之間的耦合
依賴注入:在運行過程中,當你需要這個對象是纔給你實例化並注入
SpringAOP
面向切面編程 是OO的補充 OOP從上往下 會有重複代碼 AOP將和業務無關的重複代碼抽取出來 比如權限管理、日誌、事務管理
實現AOP的方式:
- 採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行爲的執行;
- 採用靜態織入的方式,引入特定的語法創建“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼。
MySQL
InnoDB支持事務 支持外鍵
InnoDB有行級鎖,MyISAM是表級鎖
選擇MyISAM:系統插入和查詢多,不需要事務外鍵
數據庫性能優化(重點)
1、優化SQL語句和索引 在where、groupby、orderby用到的字段上建立索引
2、加緩存
3、主從複製,讀寫分離
4、垂直拆分
5、水平切分
SQL優化:
1、對經常查詢的列建立索引
2、查詢使用精確列名
3、減少子查詢,使用join
4、不用not in(使用全表掃描);不用is null not is null(會使索引和索引統計更加複雜)
在某列上建立索引,下次查詢時會使用索引查詢 最左前綴匹配原則
當表特別大 select * from table where name = xxx and age = xxx 優化策略:創建複合索引將最常用作限制條件的列放在最左邊,依次遞減。其次還要考慮該列的數據離散程度,如果有很多不同的值的話建議放在左邊,name的離散程度也大於age。
事務隔離級別
- read uncommitted:即便是事務沒有commit,但是我們仍然能讀到未提交的數據。 髒讀
- read committed: 當前會話只能讀取到其他事務提交的數據,未提交的數據讀不到。 兩次讀取的結果不同 不可重複讀
- repeatable read: 當前會話可以重複讀,就是每次讀取的結果集都相同,而不管其他事務有沒有提交。一個事務的讀取數據一致,可數據已經發生改變幻讀
- serializable(串行化):其他會話對該表的寫操作將被掛起
鎖表 鎖行
InnoDB 自動給修改操作加鎖,給查詢操作不自動加鎖
行鎖相對於表鎖來說,優勢在於高併發場景下表現更突出
表的大部分數據需要被修改,或者是多表複雜關聯查詢時,建議使用表鎖優於行鎖
悲觀鎖和樂觀鎖
悲觀鎖:select for update Synchronized
樂觀鎖:先查詢一次數據,然後使用查詢出來的數據+1進行更新數據,如果失敗則循環 適合讀多寫少
實現方式:數據版本記錄機制),使用時間戳(table增加一個字段,commit時查看是否發生了改變)(CAS compare and Swep)
索引:
原理:使用B+樹 一個節點可以存儲多個索引的key,一次磁盤IO可以讀取到很多key且葉子節點之間還加上了下一個葉子節點的指針
索引優化策略:
1、where 、groupby、orderby、on的列
2、離散度大的列
3、索引字段越小越好
水平分表:爲了解決單標數據量過大(數據量達到千萬級別)問題。所以將固定的ID hash之後mod,取若0~N個值,然後將數據劃分到不同表中,需要在寫入與查詢的時候進行ID的路由與統計
垂直分表:爲了解決表的寬度問題,同時還能分別優化每張單表的處理能力。所以將表結構根據數據的活躍度拆分成多個表,把不常用的字段單獨放到一個表、把大字段單獨放到一個表、把經常使用的字段放到一個表
分庫:面對高併發的讀寫訪問,當數據庫無法承載寫操作壓力時,不管如何擴展slave服務器,此時都沒有意義了。因此數據庫進行拆分,從而提高數據庫寫入能力,這就是分庫。
Redis
定義:鍵值對數據庫 鍵值對由字典保存 每個數據庫都有一個相應的字典(鍵空間) 鍵是一個字符串對象,值可以是包括字符串,列表,哈希表,集合,有序集合在內的任意一種Redis類型對象
主從模式:一個實例作爲主機,其餘實例作爲從機 ,數據完全一致 主機執行寫數據命令,從機執行讀數據命令, 實現讀取分離
主從模式和哨兵模式全局存取變量,浪費內存 使用集羣(分佈式存儲)
集羣的優勢在於高可用,寫的操作多且數據量巨大,且不需要高級功能則考慮集羣
哨兵的優勢在於高可用,支持高級功能,且能在讀的操作較多的場景下工作
主從的優勢在於支持高級功能,且能在讀的操作較多的場景下工作,但無法保證高可用,不建議在數據要求嚴格的場景下使用
策略:
延遲加載
讀:先從緩存讀,讀不到就從數據庫讀,讀完之後同步到緩存並添加緩存過期時間
寫:只寫數據庫
直寫
讀:先從緩存讀,讀不到就從數據庫讀,讀完之後同步到緩存且設置永不過期
寫:先寫數據庫如何同步到緩存,設置爲永不過期
如何選擇
- 如果需要緩存和數據庫保持實時一致,選擇直寫方式;
- 緩存穩定,可用空間大,寫緩存的性能丟失可用接受,選擇直寫 否則選擇延遲加載。
緩存問題
- 緩存擊穿:查詢一個數據庫不存在的數據->設置一個默認值
- 緩存失效:如果緩存失效,就會有多個進程去查詢DB,再去設置緩存,導致DB壓力大 ->將key的緩存失效時間均勻錯開
- 熱點Key:所有的流量涌向一個節點,無法通過增加機器容量解決 ->客戶端熱點key緩存設置過期時間//熱點key分散爲多個子key,散佈在不同機器
通用基礎
網絡通信協議
TCP/IP:四層網絡協議 網絡接口層 網絡層 傳輸層 應用層
HTTP
TCP/IP 應用層協議
特點
- 簡單快速(method+url)
- 靈活(傳輸任意類型的數據)
- 無連接(一次只處理一個請求,結束後釋放連接)
- 無狀態(服務器發送數據後不會記錄任何信息)
TCP
傳輸層協議 面向連接,可靠的,基於字節流 有FTP、SMTP、HTTP
建立連接(三次握手):
1、建立連接時,客戶端發送syn包(syn=j)到服務器,並進入SYN_SENT狀態,等待服務器確認;SYN:同步序列編號(Synchronize Sequence Numbers)。
2、服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
3、客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED(TCP連接成功)狀態,完成三次握手。
兩次握手可能出現網絡延遲導致創建無效連接
關閉連接:(四次揮手)
1、當主機A的應用程序通知TCP數據已經發送完畢時,TCP向主機B發送一個帶有FIN附加標記的報文段(FIN表示英文finish)。
2、主機B收到這個FIN報文段之後,並不立即用FIN報文段回覆主機A,而是先向主機A發送一個確認序號ACK,同時通知自己相應的應用程序:對方要求關閉連接(先發送ACK的目的是爲了防止在這段時間內,對方重傳FIN報文段)。
3、主機B的應用程序告訴TCP:我要徹底的關閉連接,TCP向主機A送一個FIN報文段。
4、主機A收到這個FIN報文段後,向主機B發送一個ACK表示連接徹底釋放。
爲什麼連接的時候是三次握手,關閉的時候卻是四次握手?
因爲當Server端收到Client端的SYN連接請求報文後,可以直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時,當Server端收到FIN報文時,很可能並不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴Client端,“你發的FIN報文我收到了”。只有等到我Server端所有的報文都發送完了,我才能發送FIN報文,因此不能一起發送。故需要四步握手。
滑動窗口協議
傳輸層流控 接收方告知發送方自己的窗口大小,控制發送方的發送速度
通過API通知TCP協議棧縮小TCP接收窗口
UDP
非面向連接的,不可靠的,傳輸快
HTTPS
SSL和HTTP 加密傳輸,身份認證
加密過程:
- 客戶端發送Hello消息 服務器也返回一個Hello消息 包含一些加密算法和SSL版本
- 證書交換 SSL證書包含各種數據,包含所有者名稱,相關屬性(域名),證書上的公鑰,數字簽名和關於證書有效期的信息
- 祕鑰交換 使用RSA非對稱公鑰加密算法 客戶端生成一個對稱密鑰,然後用SSL證書裏帶的服務器公鑰將該對稱密鑰加密。隨後發送到服務端,服務端用服務器私鑰解密,到此,握手階段完成。
- 加密通信 對稱加密算法
常問排序算法
快速排序
public class quickSort {
public static void quickSort(int []array,int lo,int hi){
if(lo >= hi){
return;
}
int index = partition(array,lo,hi);
quickSort(array,lo,index-1);
quickSort(array,index+1,hi);
}
public static int partition(int []array,int lo,int hi){
int tmp = array[lo];;
while(lo < hi){
while(array[hi] >=tmp && lo<hi){
hi--;
}
array[lo] = array[hi];
while(array[lo] <=tmp && lo<hi){
lo++;
}
array[hi] = array[lo];
}
array[lo] = tmp;
return lo;
}
public static void main(String args[]){
int array[] = {2,9,-1,10,4};
quickSort(array,0,array.length-1);
System.out.print(Arrays.toString(array));
}
}
冒泡排序
public class bubbbleSort {
public static void bubbleSort(int []array){
int n = array.length;
int tmp = 0;
for(int i=0;i<n-1;i++){
for(int j = 0;j< n-i-1;j++){
if(array[j+1]<array[j]){
tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
}
}
public static void main(String args[]){
int array[] = {2,9,-1,10,4};
bubbleSort(array);
System.out.print(Arrays.toString(array));
}
}
選擇排序
public class selectSort {
public static void selectSort(int array[]){
int n = array.length;
int tmp = 0;
for(int i = 0 ;i< n;i++){
int index = i;
for(int j =i;j<n;j++){
if(array[j]<array[index]){
index = j;
}
}
tmp = array[index];
array[index] = array[i];
array[i] = tmp;
}
}
public static void main(String args[]){
int array[] = {2,9,-1,10,4};
selectSort(array);
System.out.print(Arrays.toString(array));
}
}
二分查找
public class binarySearch {
public static int binarySearch(int []array,int target){
if(array.length == 0){
return 0;
}
int start = 0;
int end = array.length-1;
while(start<=end){
int mid = start + (end-start)/2;
if(array[mid]==target){
return mid;
}else if(array[mid]>target){
end = mid-1;
}else {
start = mid+1;
}
}
return 0;
}
public static void main(String []args){
int array[] = {1,2,3,4,9,10,11};
System.out.print(binarySearch(array,9));
}
}
面試算法題
在一個連續的比特流中查找特定比特塊出現的次數及首次出現的次數,比如在010011100001101110010中查找011出現的次數及首次出現的位置。
輸入:int[] array, int target,整數的取值範圍在[0,7]
輸出:出現的次數times和出現的位置,沒有找到次數返回0,位置返回-1
import java.util.ArrayList;
public class substringSearch {
static class bitRuslt{
private int times;
private int firstFound;
public int getTimes() {
return times;
}
public void setTimes(int times) {
this.times = times;
}
public int getFirstFound() {
return firstFound;
}
public void setFirstFound(int firstFound) {
this.firstFound = firstFound;
}
}
public static String intToString(int target){
if(target == 0){
return "000";
}else if(target == 1){
return "001";
}else if(target == 2){
return "010";
}else if(target == 3){
return "011";
}else if(target == 4){
return "100";
}else if(target == 5){
return "101";
}else if(target == 6){
return "110";
}else{
return "111";
}
}
public static bitRuslt find(int []array, int target){
bitRuslt result = new bitRuslt();
if(array.length == 0 || target > 7 || target < 0){
result.setTimes(0);
result.setFirstFound(-1);
return result;
}
StringBuffer arrString = new StringBuffer();
String targetStr = intToString(target);
for(int i=0;i<array.length;i++){
arrString.append(intToString(array[i]));
}
ArrayList<Integer> locations = new ArrayList<>();
int times = 0;
int firstFound = -1;
for(int i=0;i<arrString.length()-1;i++){
for(int j=0;j<3;j++){
if(targetStr.charAt(j) == arrString.charAt(i+j)){
if(j == 2){
times++;
locations.add(i);
}
continue;
}else {
break;
}
}
}
if(locations.size() == 0){
result.setFirstFound(-1);
}else {
result.setFirstFound(locations.get(0));
}
result.setTimes(times);
return result;
}
public static void main(String[]args){
int []arr = {2,3,4,1,5,6,2};
bitRuslt res = find(arr,3);
System.out.print(res.getTimes()+" "+res.getFirstFound());
}
}
持續更新中