JAVA面试八股文
目錄
- Java基礎
- 1. String 和StringBuffer和 StringBuilder的區別?
- 2. sleep() 區間wait()區間有什么區別?
- 3. Object 中有哪些方法?其中clone(),怎么實現一個對象的克隆,Java如何實現深度克隆?
- ThreadLocal 相關
- 4. ThreadLocal作用和實現方式 ?
- ThreadLocal會不會發生內存泄漏?
- ThreadLocal為什么使用弱引用?
- 5. InheritableThreadLocal作用和實現方式 ?
- 6. InheritableThreadLocal所帶來的問題?
- 7. 如何解決線程池異步值傳遞問題 (transmittable-thread-local)?
- HashMap ConcurrentHashMap相關
- 9. HashMap為什么線程不安全
- 10. HashMap在jdk7和8中的區別
- 11. HashMap 為啥將鏈表改成紅黑樹?
- 12. ConcurrentHashMap在jdk7和8中的區別?
- 提到synchronized時候,順便說一下javaSE1.6對鎖的優化?
- 偏向鎖
- 輕量級鎖
- 重量級鎖
- 其他優化
- ReentrantLock和synchronized的區別?
- Synchronized
- ReentrantLock
- 13. 為什么重寫equals時候被要求重寫hashCode()?
- 14. 什么時候回發生內存泄露?讓你寫一段內存泄露的代碼你會怎么寫?
- Java內存模型
- Java 內存模型中的 happen-before 是什么?
- 簡單聊聊volatile 的特性?以及內存語義
- GC垃圾回收
- 垃圾回收主要是針對 內存區的哪些區域?
- 垃圾檢查有哪些算法?
- 垃圾回收方法有哪些?
- 什么時候會觸發Full GC
- **GC機制**簡要說明一下,不同區使用的算法。
- 兩個對象循環引用會不會被被GC?
- 哪些可以算作根節點?
- 垃圾收集器 G1有什么樣的特性了解嗎? CMS呢?
- CMS收集器和G1收集器的區別
- Jvm相關
- Jvm內存結構簡要說一些,棧里面一般存儲了什么?
- Java內存模型簡要描述一下?
- **類加載機制**簡要描述一下?
- 類的加載方式
- 類加載的過程
- JVM三種預定義類型類加載器
- 雙親委派加載
- 由不同的類加載器加載的指定類型還是相同的類型嗎(不同)
- 在代碼中直接調用Class.forName(String name)方法,到底會觸發那個類加載器進行類加載行為?
- 在編寫自定義類加載器時,如果沒有設定父加載器,那么父加載器是?
- 編寫自定義類加載器時,一般有哪些注意點?
- 如何在運行時判斷系統類加載器能加載哪些路徑下的類?
- 在Java的反射中,Class.forName和ClassLoader的區別
- Java 類加載機制及常見異常
- ClassNotFoundException 發生在加載階段
- ClassNotFoundError 發生在 鏈接 階段
- NoClassDefFoundError 通常在鏈接階段
- Exception和Error的區別
- 平時有沒有遇到一些棧溢出或者內存溢出,內存泄露的問題嗎?如何去分析這個問題?
- 如果內存猛增,怎么去排查?
- 多線程
- 為什么《阿里巴巴Java開發手冊》強制不允許使用Executor創建線程池
- ThreadPoolExecutor機制
- 線程設置越多越好嗎?設置到什么值比較合理?
- 鎖
- CAS實現機制?
- CAS的ABA問題
- 算法
- 有哪些常用的排序算法?
- RPC框架 DUBBO
- dubbo請求流程
- dubbo 各個模塊?
- 如果zookeeper掛掉了,dubbo還能正常運行嗎?
- Dubbo 使用什么通信框架?
- Dubbo 支持哪些序列化方式?
- Dubbo 有哪些集群容錯策略?
- Dubbo 服務如何做降級?
- Dubbo 如何做限流?
- 如何自己設計一個類似 Dubbo 的 RPC 框架?
- dubbo SPI 機制 與 JAVA SPI?
- Zookeeper
- zookeeper快速選舉描述一下?
- 有了解過zk的watch機制嗎?
- 那你說說Zookeeper有哪些應用場景?
- zookeeper實現分布式鎖怎么實現?
- zookeeper集群可以部署2臺嗎?
- Redis
- redis是單線程還是雙線程?
- Redis 不僅僅是單線程
- Redis6.0為什么網絡處理要引入多線程?
- 為什么redis的性能高?什么決定的?
- redis的持久化策略?
- redis有哪些數據類型?
- 你有嘗試的去優化redis嗎?
- rdb和aof工作原理?各有什么優缺點
- 如何選擇合適的持久化方式
- Redis持久化數據和緩存怎么做擴容?
- Redis 對過期數據的處理
- LRU(the least recently used 最近最少使用)算法
- Redis線程模型
- 官方Redis Cluster 方案(服務端路由查詢)
- Redis的哨兵模式
- 基于代理服務器分片
- redis的有哪些主從同步方式?
- redis集群擴容與收縮
- Redis底層ZSet跳表是如何設計與實現的
- Redis底層ZSet實現壓縮列表和跳表如何選擇
- Redis高并發場景熱點緩存如何重建
- 高并發場景緩存穿透&失效&雪崩如何解決
- Redis集群架構如何抗住雙十一的洪峰流量
- Redis緩存與數據庫雙寫不一致如何解決
- Redis分布式鎖主從架構鎖失效問題如何解決
- 從CAP角度解釋下Redis&Zookeeper鎖架構異同
- 超大并發的分布式鎖架構該如何設計
- 雙十一億級用戶日活統計如何用Redis快速計算
- 雙十一電商推薦系統如何用Redis實現
- 雙十一電商購物車系統如何用Redis實現
- 類似微信的社交App朋友圈關注模型如何設計實現
- 美團單車如何基于Redis快速找到附近的車
- Redis 6.0 多線程模型比單線程優化在哪里了
- Spring
- 請簡要描述一下IOC 和AOP?
- Spring是怎么解決的循環依賴?
- 為啥Spring不能解決“A的構造方法中依賴了B的實例對象
- Spring 中使用的那種方式來實現動態代理的?
- Spring中的事務傳播機制?事務嵌套
- Spring中同一個類中有方法A 和 B 兩個方法都被標記了@Transtional,在A中調用了B,那么B的事務會生效嗎?為什么?
- Spring 中IOC 和工廠模式的區別,為啥不用工廠模式?
- 網絡
- select、poll、epoll之間的區別
- BIO、NIO、AIO
- BIO
- NIO
- NIO 和 BIO 對比
- netty是用select 還是 epoll
- Channel 的基本介紹
- 說說Reactor線程模型的理解
- AIO
- Reactor 單線程
- Reactor 多線程
- Reactor 主從
- TCP三次握手四次揮手
- 四次揮手中TIME_WAIT狀態存在的目的是什么?
- TCP是通過什么機制保障可靠性的?
- TCP粘包,拆包及解決方法
- 操作系統虛擬內存換頁的過程
- kafka
- Sentinel的簡單原理
- `服務隔離機制:`線程池隔離或者信號量隔離機制
- 分布式鎖
- 數據庫樂觀鎖
- Redis分布式鎖
- Mysql
- 本地事務
- 隔離型(Isolation) 主要由MVCC和鎖實現
- MVCC 多版本并發控制
- 回滾日志 `undo log`
- 事務日志 `REDO LOG` Write Ahead Log(WAL)策略
- 鎖
- 索引
- 索引類型
- 高性能索引的一些策略
- 什么是聯合索引,為什么建議盡量用聯合索引
- 什么是覆蓋索引,以及優點?
- 為什么使用B+樹,而不用其他的例如紅黑樹做索引
- InnoDB 與 MyISAM 結構上的區別
- 什么是索引下推
- 分區分表分庫
- 項目介紹
- 秒殺系統
- 你做的這個秒殺系統QPS怎么樣
- 你怎么判斷需要多少機器來承受?
- 如果判斷一個機器達到了極限?CPU IO 磁盤?
- 緩存穿透 緩存擊穿 緩存雪崩
- 漏桶算法和令牌桶算法
- 操作系統
- 什么是零拷貝
- 什么是 DMA (DMA控制器Direct Memory Access)
- 傳統拷貝方式
- 利用 mmap()實現零拷貝 **`應用程序跟操作系統共享這個緩沖區(地址映射)`** 用戶空間可以修改數據
- sendfile() 方式零拷貝 **`不僅減少了數據拷貝操作,它也減少了上下文切換`** 但是用戶空間不可修改數據
- 帶有 DMA 收集拷貝功能的 sendfile() **`不拷貝內容,只拷貝描述符(帶地址和偏移量)`**
- 利用寫時復制
- Java NIO對文件映射mmap的支持
- Java NIO對sendfile的支持就是
- Spring
- Spring cloud
- 開放性問題
- 你覺得一個好的團隊應該是什么樣的?
- 解決問題的能力
- 平時會使用設計模式嗎?請講一個使用的情景
- 工作中有碰到過線上的問題嗎?怎么解決的?
- 你有過Jvm調優經驗嗎?
- 你做了這么多系統,是怎么做系統監控的,系統的穩定性?內存滿了,磁盤滿了,和cpu高了之類的?
- 有碰到過數據庫死鎖的問題嗎?怎么解決的?
- 算法
- 滑動窗口
- 反轉單鏈表
- 復雜鏈表復制
- 數組a,先單調地址再單調遞減,輸出數組中不同元素個數。要求:O(1)空間復雜度,不能改變原數組
- 給出兩個升序數組A、B和長度m、n,求第k個大的數
- 給出數組A,長度為n,數組中元素的值位于[0, n - 1]之間,求是否有重復元素
- 鏡像二叉樹
- 二叉樹多個節點的最近公共祖先
- 樹的非遞歸先序遍歷。
Java基礎
1. String 和StringBuffer和 StringBuilder的區別?
String,StringBuffer與StringBuilder的區別??
String 字符串常量
StringBuffer 字符串變量(線程安全)
StringBuilder 字符串變量(非線程安全)
2. sleep() 區間wait()區間有什么區別?
sleep 是Thread中的方法,線程暫停,讓出CPU,但是不釋放鎖🔐
wait()是Object中的方法, 調用次方法必須讓當前線程必須擁有此對象的monitor(即鎖),執行之后 線程阻塞,讓出CPU, 同時也釋放鎖🔐; 等待期間不配擁有CPU執行權, 必須調用notify/notifyAll方法喚醒,(notify是隨機喚醒) 喚醒并不意味著里面就會執行,而是還是需要等待分配到CPU才會執行;
3. Object 中有哪些方法?其中clone(),怎么實現一個對象的克隆,Java如何實現深度克隆?
clone是淺拷貝;只克隆了自身對象和對象內實例變量的地址引用,使用它需要實現接口Cloneable;
使用ObjectStream進行深度克隆; 先將對象序列化;然后再反序列化;
ThreadLocal 相關
4. ThreadLocal作用和實現方式 ?
TL用于保存本地線程的值, 每個Thread都有一個threadLocals屬性,它是一個ThreadLocalMap對象,本質上是一個Entry數組;Entry是k-v結構; 并且是WeakReference弱引用, K存的是 Thread對象,Value是設置的值; 那么每個線程就可以讀自己設置的值了;
ThreadLocal會不會發生內存泄漏?
會發生內存泄漏
ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部強引用來引用它,那么系統 GC 的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當前線程再遲遲不結束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠無法回收,造成內存泄漏。
其實,ThreadLocalMap的設計中已經考慮到這種情況,也加上了一些防護措施:在ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap里所有key為null的value。
ThreadLocal為什么使用弱引用?
key是弱引用好歹還可以 GC掉key的對象;強引用則不行
使用弱引用可以多一層保障:弱引用ThreadLocal不會內存泄漏,對應的value在下一次ThreadLocalMap調用set,get,remove的時候會被清除。
5. InheritableThreadLocal作用和實現方式 ?
InheritableThreadLocal基礎 ThreadLocal ; 他跟ThreadLocal區別是 可以傳遞值給子線程; 每個Thread都有一個inheritableThreadLocals屬性, 創建子線程的時候,把把父線程的Entry數組 塞到子線程的Entry數組中; 所以就實現了父子線程的值傳遞; 注意如果Value是一個非基本類型的對象, 父子線程指向的是相同的引用; 子線程如果修改了值,父線程也是會修改的;
6. InheritableThreadLocal所帶來的問題?
線程不安全: 如果說線程本地變量是只讀變量不會受到影響,但是如果是可寫的,那么任意子線程針對本地變量的修改都會影響到主線程的本地變量
線程池中可能失效: 在使用線程池的時候,ITL會完全失效,因為父線程的TLMap是通過Thread的init方法的時候進行賦值給子線程的,而線程池在執行異步任務時可能不再需要創建新的線程了,因此也就不會再傳遞父線程的TLMap給子線程了
7. 如何解決線程池異步值傳遞問題 (transmittable-thread-local)?
阿里開源的transmittable-thread-local可以很好的解決 在線程池情況下,父子線程值傳遞問題;TransmittableThreadLocal繼承了InheritableThreadLocal, 簡單的原理就是TTL 中的holder持有的是當前線程內的所有本地變量,被包裝的run方法執行異步任務之前,會使用replay進行設置父線程里的本地變量給當前子線程,任務執行完畢,會調用restore恢復該子線程原生的本地變量
HashMap ConcurrentHashMap相關
9. HashMap為什么線程不安全
1.在JDK1.7中,當并發執行擴容操作時會造成環形鏈和數據丟失的情況。(鏈表的頭插法 造成環形鏈)
2.在JDK1.8中,在并發執行put操作時會發生數據覆蓋的情況。(元素插入時使用的是尾插法)
HashMap在put的時候,插入的元素超過了容量(由負載因子決定)的范圍就會觸發擴容操作,就是rehash,這個會重新將原數組的內容重新hash到新的擴容數組中,在多線程的環境下,存在同時其他的元素也在進行put操作,如果hash值相同,可能出現同時在同一數組下用鏈表表示,造成閉環,導致在get時會出現死循環,所以HashMap是線程不安全的。
JDK1.7和JDK1.8中HashMap為什么是線程不安全的
10. HashMap在jdk7和8中的區別
Hashmap的結構,1.7和1.8有哪些區別
11. HashMap 為啥將鏈表改成紅黑樹?
提高檢索時間,在鏈表長度大于8的時候,將后面的數據存在紅黑樹中,以加快檢索速度。復雜度變成O(logn)
12. ConcurrentHashMap在jdk7和8中的區別?
可以看出JDK1.8版本的ConcurrentHashMap的數據結構已經接近HashMap,相對而言,ConcurrentHashMap只是增加了同步的操作來控制并發,從JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+紅黑樹,相對而言
提到synchronized時候,順便說一下javaSE1.6對鎖的優化?
在JDK1.5中,synchronized是性能低效的。因為這是一個重量級操作,它對性能大的影響是阻塞的是實現,掛起 線程和恢復線程的操作都需要轉入內核態中完成,這些操作給系統的并發性帶來了很大的壓力
javaSE1.6引入了偏向鎖,輕量級鎖(自旋鎖)后,synchronized和ReentrantLock兩者的性能就差不多了
鎖可以升級, 但不能降級. 即: 無鎖 -> 偏向鎖 -> 輕量級鎖 -> 重量級鎖是單向的.
偏向鎖
偏向鎖: HotSpot的作者經過研究發現,大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得; 偏向鎖是四種狀態中最樂觀的一種鎖:從始至終只有一個線程請求某一把鎖。
偏向鎖的獲取: 當一個線程訪問同步塊并成功獲取到鎖時,會在對象頭和棧幀中的鎖記錄字段里存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,直接進入
偏性鎖的撤銷: 偏向鎖使用了一種等待競爭出現才釋放鎖的機制,所以當其他線程競爭偏向鎖時,持有偏向鎖的線程才會釋放偏向鎖,并將鎖膨脹為輕量級鎖(持有偏向鎖的線程依然存活的時候)
輕量級鎖
多個線程在不同的時間段請求同一把鎖,也就是說沒有鎖競爭。
加鎖: 線程在執行同步塊之前,JVM會先在當前線程的棧楨中創建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復制到鎖記錄中,官方稱為Displaced Mark Word。然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。
解鎖:輕量級鎖解鎖時, 會使用原子的CAS操作將當前線程的鎖記錄替換回到對象頭, 如果成功, 表示沒有競爭發生; 如果失敗, 表示當前鎖存在競爭, 鎖就會膨脹成重量級鎖.
重量級鎖
Java線程的阻塞以及喚醒,都是依靠操作系統來完成的,這些操作將涉及系統調用,需要從操作系統 的用戶態切換至內核態,其開銷非常之大。
其他優化
鎖粗化:鎖粗化就是將多次連接在一起的加鎖、解鎖操作合并為一次,將多個連續的鎖擴展成為一個范圍更大的鎖
鎖消除:鎖消除即刪除不必要的加鎖操作。根據代碼逃逸技術,如果判斷到一段代碼中,堆上的數據不會逃逸出當前線程, 那么可以認為這段代碼是線程安全的,不必要加鎖
ReentrantLock和synchronized的區別?
在HotSpot虛擬機中, 對象在內存中的布局分為三塊區域: 對象頭, 示例數據和對其填充.
對象頭中包含兩部分: MarkWord 和 類型指針.
多線程下synchronized的加鎖就是對同一個對象的對象頭中的MarkWord中的變量進行CAS操作
Synchronized
對于Synchronized來說,它是java語言的關鍵字,是原生語法層面的互斥,需要jvm實現,Synchronized的使用比較方便簡潔,并且由編譯器去保證鎖的加鎖和釋放
ReentrantLock
ReenTrantLock的實現是一種自旋鎖,通過循環調用CAS操作來實現加鎖。它的性能比較好也是因為避免了使線程進入內核態的阻塞狀態。
13. 為什么重寫equals時候被要求重寫hashCode()?
如果兩個對象相同(即:用 equals 比較返回true),那么它們的 hashCode 值一定要相同
如果兩個對象的 hashCode 相同,它們并不一定相同(即:用 equals 比較返回 false
為了提供程序效率 通常會先進性hashcode的比較,如果不同,則就么有必要equals比較了;
14. 什么時候回發生內存泄露?讓你寫一段內存泄露的代碼你會怎么寫?
JAVA 內存泄露詳解(原因、例子及解決)
Java中關于內存泄漏出現的原因以及如何避免內存泄漏
我們知道,對象都是有生命周期的,有的長,有的短,如果長生命周期的對象持有短生命周期的引用,就很可能會出現內存泄露
下面給出一個 Java 內存泄漏的典型例子,
Vector v = new Vector(10);for (int i = 0; i < 100; i++) {Object o = new Object();v.add(o);o = null;}在這個例子中,我們循環申請Object對象,并將所申請的對象放入一個 Vector 中,如果我們僅僅釋放引用本身,那么 Vector 仍然引用該對象,所以這個對象對 GC 來說是不可回收的。因此,如果對象加入到Vector 后,還必須從 Vector 中刪除,最簡單的方法就是將 Vector 對象設置為 null。
v = null
ThreadLocal使用不當也可能泄漏
Java內存模型
在共享內存的并發模型里,線程之間共享程序的公共狀態,線程之間通過寫 - 讀內存中的公共狀態來隱式進行通信。Java 的并發采用的是共享內存模型
線程之間的共享變量存儲在主內存(main memory)中,每個線程都有一個私有的本地內存(local memory)
Java 內存模型中的 happen-before 是什么?
從 JDK5 開始,java 使用新的 JSR -133 內存模型,提出了 happens-before 的概念
如果一個操作執行的結果需要對另一個操作可見,那么這兩個操作之間必須存在 happens-before 關系這里提到的兩個操作既可以是在一個線程之內,也可以是在不同線程之間
注意,兩個操作之間具有 happens-before 關系,并不意味著前一個操作必須要在后一個操作之前執行!
簡單聊聊volatile 的特性?以及內存語義
可見性。對一個 volatile 變量的讀,總是能看到(任意線程)對這個 volatile 變量最后的寫入。
原子性:對任意單個 volatile 變量的讀 / 寫具有原子性,但類似于 volatile++ 這種復合操作不具有原子性。
volatile 寫的內存語義:當寫一個 volatile 變量時,JMM 會把該線程對應的本地內存中的共享變量刷新到主內存
volatile 讀的內存語義: 當讀一個 volatile 變量時,JMM 會把該線程對應的本地內存置為無效。線程接下來將從主內存中讀取共享變量
為了實現 volatile 的內存語義,編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序。 JMM 采取保守策略
在每個 volatile 寫操作的前面插入一個 StoreStore 屏障。
在每個 volatile 寫操作的后面插入一個 StoreLoad 屏障。
在每個 volatile 讀操作的后面插入一個 LoadLoad 屏障。
在每個 volatile 讀操作的后面插入一個 LoadStore 屏障。
通過反編譯可以看到,有volatile變量修飾的遍歷,會有一個lock前綴的指令,lock前綴的指令在多核處理器下會引發了兩件事情
將當前處理器緩存行的數據會寫回到系統內存。
這個寫回內存的操作會引起在其他CPU里緩存了該內存地址的數據無效。
GC垃圾回收
垃圾回收主要是針對 內存區的哪些區域?
主要追對的是 Java堆 和 方法區 ;
java棧、程序計數器、本地方法棧都是線程私有的,線程生就生,線程滅就滅,棧中的棧幀隨著方法的結束也會撤銷,內存自然就跟著回收了。所以這幾個區域的內存分配與回收是確定的,我們不需要管的。但是java堆和方法區則不一樣,我們只有在程序運行期間才知道會創建哪些對象,所以這部分內存的分配和回收都是動態的。一般我們所說的垃圾回收也是針對的這一部分。
垃圾檢查有哪些算法?
好了,問題來了,如果我有兩個對象A和B,互相引用,除此之外,沒有其他任何對象引用它們,實際上這兩個對象已經無法訪問,即是我們說的垃圾對象。但是互相引用,計數不為0,導致無法回收,所以還有另一種方法:
垃圾回收方法有哪些?
年輕代(Young Generation)的回收算法 (回收主要以Copying為主)
年老代(Old Generation)的回收算法(回收主要以Mark-Compact為主)
什么時候會觸發Full GC
(1)調用System.gc時,系統建議執行Full GC,但是不必然執行
(2)老年代空間不足
(3)方法區(1.8之后改為元空間)空間不足
(4)創建大對象,比如數組,通過Minor GC后,進入老年代的平均大小大于老年代的可用內存
(5)由Eden區、From Space區向To Space區復制時,對象大小大于To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小于該對象大小。
GC機制簡要說明一下,不同區使用的算法。
年輕代:是所有新對象產生的地方。年輕代被分為3個部分——Enden區和兩個Survivor區(From和to)當Eden區被對象填滿時,就會執行Minor GC。并把所有存活下來的對象轉移到其中一個survivor區(假設為from區)。Minor GC同樣會檢查存活下來的對象,并把它們轉移到另一個survivor區(假設為to區)。這樣在一段時間內,總會有一個空的survivor區。經過多次GC周期后,仍然存活下來的對象會被轉移到年老代內存空間。通常這是在年輕代有資格提升到年老代前通過設定年齡閾值來完成的。需要注意,Survivor的兩個區是對稱的,沒先后關系,from和to是相對的。
年老代:在年輕代中經歷了N次回收后仍然沒有被清除的對象,就會被放到年老代中,可以說他們都是久經沙場而不亡的一代,都是生命周期較長的對象。對于年老代和永久代,就不能再采用像年輕代中那樣搬移騰挪的回收算法,因為那些對于這些回收戰場上的老兵來說是小兒科。通常會在老年代內存被占滿時將會觸發Full GC,回收整個堆內存。
持久代:用于存放靜態文件,比如java類、方法等。持久代對垃圾回收沒有顯著的影響
兩個對象循環引用會不會被被GC?
GC里邊在JVM當中是使用的ROOT算法,ROOT算法 也就是根; 只要看這個兩個對象有沒有掛在 根 上, 掛在根上了 就不會被回收; 沒有掛在根上就會回收;
哪些可以算作根節點?
垃圾收集器 G1有什么樣的特性了解嗎? CMS呢?
Cms與G1的優缺點
CMS垃圾回收器:
這個階段,重新標記的內存范圍是整個堆,包含_young_gen和_old_gen。為什么要掃描新生代呢,因為對于老年代中的對象,如果被新生代中的對象引用,那么就會被視為存活對象,即使新生代的對象已經不可達了
CMS垃圾回收器的優化
1.減少重新標記remark階段停頓
一般CMS的GC耗時 80%都在remark階段,如果發現remark階段停頓時間很長,可以嘗試添加該參數:-XX:+CMSScavengeBeforeRemark
在執行remark操作之前先做一次ygc,目的在于減少ygen對oldgen的無效引用,降低remark時的開銷。
G1垃圾回收器
CMS收集器和G1收集器的區別
區別一: 使用范圍不一樣
CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用
G1收集器收集范圍是老年代和新生代。不需要結合其他收集器使用
區別二: STW的時間
CMS收集器以最小的停頓時間為目標的收集器。
G1收集器可預測垃圾回收的停頓時間(建立可預測的停頓時間模型)
區別三: 垃圾碎片
CMS收集器是使用“標記-清除”算法進行的垃圾回收,容易產生內存碎片
G1收集器使用的是“標記-整理”算法,進行了空間整合,降低了內存空間碎片。
Jvm相關
Jvm內存結構簡要說一些,棧里面一般存儲了什么?
Java內存模型簡要描述一下?
類加載機制簡要描述一下?
虛擬機把描述類的數據從Class文件加載到內存,并對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的java類型。類加載和連接的過程都是在運行期間完成的。
類的加載方式
1):本地編譯好的class中直接加載
2):網絡加載:java.net.URLClassLoader可以加載url指定的類
3):從jar、zip等等壓縮文件加載類,自動解析jar文件找到class文件去加載util類
4):從java源代碼文件動態編譯成為class文件
類加載的過程
加載
a)加載階段的工作
i.通過一個類的全限定名來獲取定義此類的二進制字節流。
ii.將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。
iii.在java堆中生成一個代表這個類的java.lang.Class對象,做為方法區這些數據的訪問入口。
b)加載階段完成之后二進制字節流就按照虛擬機所需的格式存儲在方區去中。
驗證
這一階段的目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求。
準備
準備階段是正式為變量分配內存并設置初始值,這些內存都將在方法區中進行分配,這里的變量僅包括類標量不包括實例變量。
解析
解析是虛擬機將常量池的符號引用替換為直接引用的過程。
初始化
初始化階段是執行類構造器()方法的過程
JVM三種預定義類型類加載器
a. Bootstrap ClassLoader/啟動類加載器
主要負責jdk_home/lib目錄下的核心 api 或 -Xbootclasspath 選項指定的jar包裝入工作.
b. Extension ClassLoader/擴展類加載器
主要負責jdk_home/lib/ext目錄下的jar包或 -Djava.ext.dirs 指定目錄下的jar包裝入工作
c. System ClassLoader/系統類加載器
主要負責java -classpath/-Djava.class.path所指的目錄下的類與jar包裝入工作.
d. User Custom ClassLoader/用戶自定義類加載器(java.lang.ClassLoader的子類)
在程序運行期間, 通過java.lang.ClassLoader的子類動態加載class文件, 體現java動態實時類裝入特性.
雙親委派加載
JVM在加載類時默認采用的是雙親委派機制, 先往上 讓上層加載器去加載
由不同的類加載器加載的指定類型還是相同的類型嗎(不同)
在Java中,一個類用其完全匹配類名(fully qualified class name)作為標識,這里指的完全匹配類名包括包名和類名。但在JVM中一個類用其全名和一個加載類ClassLoader的實例作為唯一標識,不同類加載器加載的類將被置于不同的命名空間. 所以是不相同的
在代碼中直接調用Class.forName(String name)方法,到底會觸發那個類加載器進行類加載行為?
Class.forName(String name)默認會使用調用類的類加載器來進行類加載
在編寫自定義類加載器時,如果沒有設定父加載器,那么父加載器是?
在不指定父類加載器的情況下,默認采用系統類加載器(AppClassLoader);
編寫自定義類加載器時,一般有哪些注意點?
如何在運行時判斷系統類加載器能加載哪些路徑下的類?
一 是可以直接調用ClassLoader.getSystemClassLoader()或者其他方式獲取到系統類加載器(系統類加載器和擴展類加載器本身都派生自URLClassLoader),調用URLClassLoader中的getURLs()方法可以獲取到;
二 是可以直接通過獲取系統屬性java.class.path 來查看當前類路徑上的條目信息 , System.getProperty("java.class.path")
在Java的反射中,Class.forName和ClassLoader的區別
ClassLoader就是遵循雙親委派模型最終調用啟動類加載器的類加載器
Class.forName()方法實際上也是調用的CLassLoader來實現的;在這個forName0方法中的第二個參數被默認設置為了true,這個參數代表是否對加載的類進行初始化,設置為true時會類進行初始化,代表會執行類中的靜態代碼塊,以及對靜態變量的賦值等操作。
Class.forName 默認會進行初始化,執行靜態代碼塊;有參數可以設置
Java 類加載機制及常見異常
ClassNotFoundException 發生在加載階段
無法找到目標類
通常加載方式 Class.forName / ClassLoader.loadClass ;
導致原因:1、類名拼寫錯誤或者沒有拼寫完整類名
2,沒有導入相應的jar包
ClassNotFoundError 發生在 鏈接 階段
類加載過程有幾個階段
讀取:找到.class文件,讀取
鏈接:校驗讀取的class文件是否符合規范
初始化:載入靜態資源 靜態塊 產生一個Class對象
NoClassDefFoundError 通常在鏈接階段
Exception和Error的區別
首先Exception和Error都是繼承于Throwable 類,在 Java 中只有 Throwable 類型的實例才可以被拋出(throw)或者捕獲(catch),它是異常處理機制的基本組成類型。
Exception是java程序運行中可預料的異常情況,咱們可以獲取到這種異常,并且對這種異常進行業務外的處理。
Error是java程序運行中不可預料的異常情況,這種異常發生以后,會直接導致JVM不可處理或者不可恢復的情況。所以這種異常不可能抓取到,比如OutOfMemoryError、NoClassDefFoundError等。
平時有沒有遇到一些棧溢出或者內存溢出,內存泄露的問題嗎?如何去分析這個問題?
內存泄漏是指對象實例在新建和使用完畢后,仍然被引用,沒能被垃圾回收釋放,一直積累,直到沒有剩余內存可用。如果內存泄露,我們要找出泄露的對象是怎么被GC ROOT引用起來,然后通過引用鏈來具體分析泄露的原因。分析內存泄漏的工具有:Jprofiler,visualvm等。
內存溢出是指當我們新建一個實力對象時,實例對象所需占用的內存空間大于堆的可用空間。
棧(JVM Stack)存放主要是棧幀( 局部變量表, 操作數棧 , 動態鏈接 , 方法出口信息 )的地方。注意區分棧和棧幀:棧里包含棧幀。與線程棧相關的內存異常有兩個:
a)、StackOverflowError(方法調用層次太深,內存不夠新建棧幀)
b)、OutOfMemoryError(線程太多,內存不夠新建線程)
如果出現了內存溢出問題,這往往是程序本生需要的內存大于了我們給虛擬機配置的內存,這種情況下,我們可以采用調大-Xmx來解決這種問題
如果內存猛增,怎么去排查?
通過jstack分析問題
1、利用top名稱查看哪個java進程占用了較多的cpu資源;
2、通過top -Hp pid可以查看該進程下各個線程的cpu使用情況;
3.通過top -Hp命令定位到cpu占用率較高的線程tid之后,繼續使用jstack pid命令查看當前java進程的堆棧狀態
4.然后將剛剛找到的tid轉換成16進制,在 jstack -pid里面的堆棧信息里面找到對應的線程信息
多線程
為什么《阿里巴巴Java開發手冊》強制不允許使用Executor創建線程池
線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險
主要是Executor的一些方法創建的線程池的對了長度都非常大,容易堆積大量的請求,從而導致OOM
ThreadPoolExecutor機制
下面是ThreadPoolExecutor最核心的構造方法參數:
1)corePoolSize核心線程池的大小
2)maximumPoolSize 最大線程池大小,當隊列滿了 就會創建新線程直至最大
3)keepAliveTime 線程池中超過corePoolSize數目的空閑線程最大存活時間;可以allowCoreThreadTimeOut(true)使得核心線程超出有效時間也關閉
4)TimeUnit keepAliveTime的時間單位
5)workQueue阻塞任務隊列
6)threadFactory新建線程工廠,可以自定義工廠
7)RejectedExecutionHandler當提交任務數超過maximumPoolSize+workQueue之和時,任務會交給RejectedExecutionHandler來處理
重點講解
corePoolSize,maximumPoolSize,workQueue三者之間的關系
1)當線程池小于corePoolSize時,新提交的任務會創建一個新線程執行任務,即使線程池中仍有空閑線程。
2)當線程池達到corePoolSize時,新提交的任務將被放在workQueue中,等待線程池中的任務執行完畢
3)當workQueue滿了,并且maximumPoolSize > corePoolSize時,新提交任務會創建新的線程執行任務
4)當提交任務數超過maximumPoolSize,新任務就交給RejectedExecutionHandler來處理
5)當線程池中超過 corePoolSize線程,空閑時間達到keepAliveTime時,關閉空閑線程
6)當設置allowCoreThreadTimeOut(true)時,線程池中corePoolSize線程空閑時間達到keepAliveTime也將關閉
RejectedExecutionHandler拒絕策略
1、AbortPolicy策略:該策略會直接拋出異常,阻止系統正常工作;
2、CallerRunsPolicy策略:如果線程池的線程數量達到上限,該策略會把任務隊列中的任務放在調用者線程當中運行;
3、DiscardOledestPolicy策略:該策略會丟棄任務隊列中最老的一個任務,也就是當前任務隊列中最先被添加進去的,馬上要被執行的那個任務,并嘗試再次提交;
4、DiscardPolicy策略:該策略會默默丟棄無法處理的任務,不予任何處理。當然使用此策略,業務場景中需允許任務的丟失;
也可以自己擴展RejectedExecutionHandler接口
workQueue任務隊列
線程設置越多越好嗎?設置到什么值比較合理?
鎖
CAS實現機制?
內存中value的偏移量 long valueOffset = Unsafe.getUnsafe().objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}通過本地方法Unsafe.getUnsafe().objectFieldOffset獲取 值 在內存中的偏移量;然后又通過本地方法unsafe.compareAndSwapInt 去更新數據; 如果內存中的值跟期望中的值一樣則 修改成update;
CAS的ABA問題
如線程1從內存X中取出A,這時候另一個線程2也從內存X中取出A,并且線程2進行了一些操作將內存X中的值變成了B,然后線程2又將內存X中的數據變成A,這時候線程1進行CAS操作發現內存X中仍然是A,然后線程1操作成功。雖然線程1的CAS操作成功,但是整個過程就是有問題的。比如鏈表的頭在變化了兩次后恢復了原值,但是不代表鏈表就沒有變化
所以JAVA中提供了AtomicStampedReference/AtomicMarkableReference來處理會發生ABA問題的場景,主要是在對象中額外再增加一個標記來標識對象是否有過變更
算法
有哪些常用的排序算法?
冒泡算法、選擇排序、插入排序、希爾排序、歸并排序、快速排序
RPC框架 DUBBO
Dubbo缺省協議采用單一長連接和NIO異步通訊
適合于小數據量大并發的服務調用,以及服務消費者機器數遠大于服務提供者機器數的情況
dubbo請求流程
dubbo 各個模塊?
Service 業務層:業務代碼的接口與實現
config 配置層:對外配置接口,以 ServiceConfig, ReferenceConfig 為中心,可以直接初始化配置類,也可以通過 Spring 解析配置生成配置類。
proxy 服務代理層:服務接口透明代理
registry 注冊中心層:封裝服務地址的注冊與發現
cluster 路由層:封裝多個提供者的路由及負載均衡
monitor 監控層:RPC 調用次數和調用時間監控
如果zookeeper掛掉了,dubbo還能正常運行嗎?
能,本地有保存一份數據;
Dubbo 使用什么通信框架?
在 Dubbo 的最新版本,默認使用 Netty4 的版本
當然你也可以通過SPI 選擇Netty3 Mina Grizzly
Dubbo 支持哪些序列化方式?
【重要】Hessian2 :基于 Hessian 實現的序列化拓展。dubbo:// 協議的默認序列化方案
Dubbo :Dubbo 自己實現的序列化拓展
還有 Kryo 、FST、JSON、NativeJava、CompactedJava
Dubbo 有哪些集群容錯策略?
Failover Cluster[默認]: 失敗自動重試其他服務的策略。
Failover Cluster : 失敗自動切換,當出現失敗,重試其它服務器。通常用于讀操作,但重試會帶來更長延遲。可通過 retries=“2” 來設置重試次數(不含第一次)。
Failfast Cluster:快速失敗,只發起一次調用,失敗立即報錯。通常用于非冪等性的寫操作,比如新增記錄。
Failsafe Cluster: 失敗安全,出現異常時,直接忽略。通常用于寫入審計日志等操作
Failback Cluster: 失敗自動恢復,后臺記錄失敗請求,定時重發。通常用于消息通知操作。
Forking Cluster: 并行調用多個服務器,只要一個成功即返回。通常用于實時性要求較高的讀操作,但需要浪費更多服務資源。可通過 forks=“2” 來設置最大并行數。
Broadcast Cluster: 廣播調用所有提供者,逐個調用,任意一臺報錯則報錯。通常用于通知所有提供者更新緩存或日志等本地資源信息。
Dubbo 服務如何做降級?
Dubbo 如何做限流?
如何自己設計一個類似 Dubbo 的 RPC 框架?
舉個栗子,我給大家說個最簡單的回答思路:
這就是一個最最基本的 rpc 框架的思路,先不說你有多牛逼的技術功底,哪怕這個最簡單的思路你先給出來行不行?
dubbo SPI 機制 與 JAVA SPI?
這里寫鏈接內容
Zookeeper
zookeeper快速選舉描述一下?
Zookeeper的核心是原子廣播,這個機制保證了各個Server之間的同步。實現這個機制的協議叫做Zab協議。Zab協議有兩種模式,它們分別是恢復模式(選主)和廣播模式(同步)。當服務啟動或者在領導者崩潰后,Zab就進入了恢復模式,當領導者被選舉出來,且大多數Server完成了和leader的狀態同步以后,恢復模式就結束了。狀態同步保證了leader和Server具有相同的系統狀態。leader選舉是保證分布式數據一致性的關鍵。
當zk集群中的一臺服務器出現以下兩種情況之一時,就會開始leader選舉。
(1)服務器初始化啟動。
(2)服務器運行期間無法和leader保持連接。
而當一臺機器進入leader選舉流程時,當前集群也可能處于以下兩種狀態。
(1)集群中本來就已經存在一個leader。
(2)集群中確實不存在leader。
首先第一種情況,通常是集群中某一臺機器啟動比較晚,在它啟動之前,集群已經正常工作,即已經存在一臺leader服務器。當該機器試圖去選舉leader時,會被告知當前服務器的leader信息,它僅僅需要和leader機器建立連接,并進行狀態同步即可。
開始選舉
sid:即server id,用來標識該機器在集群中的機器序號。
zxid:即zookeeper事務id號。
ZooKeeper狀態的每一次改變, 都對應著一個遞增的Transaction id,,該id稱為zxid.,由于zxid的遞增性質, 如果zxid1小于zxid2,,那么zxid1肯定先于zxid2發生。創建任意節點,或者更新任意節點的數據, 或者刪除任意節點,都會導致Zookeeper狀態發生改變,從而導致zxid的值增加。
以(sid,zxid)的形式來標識一次投票信息。
(1)初始階段,都會給自己投票。
(2)當接收到來自其他服務器的投票時,都需要將別人的投票和自己的投票進行pk,規則如下:
優先檢查zxid。zxid比較大的服務器優先作為leader。如果zxid相同的話,就比較sid,sid比較大的服務器作為leader。
有了解過zk的watch機制嗎?
客戶端watcher 可以監控節點的數據變化以及它子節點的變化,一旦這些狀態發生變化,zooKeeper服務端就會通知所有在這個節點上設置過watcher的客戶端 ,從而每個客戶端都很快感知,它所監聽的節點狀態發生變化,而做出對應的邏輯處理。
watch對節點的監聽事件是一次性的
那你說說Zookeeper有哪些應用場景?
zookeeper實現分布式鎖怎么實現?
每個watch只會通知一次,鎖具有順序性,并且watch自己前面的一個節點是為了避免羊群效應
zookeeper集群可以部署2臺嗎?
Redis
redis是單線程還是雙線程?
家所熟知的 Redis 確實是單線程模型,指的是執行 Redis 命令的核心模塊是單線程的,而不是整個 Redis 實例就一個線程,Redis 其他模塊還有各自模塊的線程的。
Redis基于Reactor模式開發了網絡事件處理器,這個處理器被稱為文件事件處理器。它的組成結構為4部分:多個套接字、IO多路復用程序、文件事件分派器、事件處理器。
因為文件事件分派器隊列的消費是單線程的`,所以Redis才叫單線程模型。
Redis 不僅僅是單線程
一般來說 Redis 的瓶頸并不在 CPU,而在內存和網絡。如果要使用 CPU 多核,可以搭建多個 Redis 實例來解決。
其實,Redis 4.0 開始就有多線程的概念了,比如 Redis 通過多線程方式在后臺刪除對象、以及通過 Redis 模塊實現的阻塞命令等。
Redis6.0為什么網絡處理要引入多線程?
內存不夠的話,可以加內存或者做數據結構優化和其他優化等 但網絡的性能優化才是大頭,網絡 IO 的讀寫在 Redis 整個執行期間占用了大部分的 CPU 時間,如果把網絡處理這部分做成多線程處理方式,那對整個 Redis 的性能會有很大的提升。Redis 的多線程部分只是用來處理網絡數據的讀寫和協議解析,執行命令仍然是單線程
為什么redis的性能高?什么決定的?
redis的持久化策略?
Redis 提供兩種持久化機制 RDB(默認) 和 AOF 機制:
redis有哪些數據類型?
數據結構豐富,除了支持string類型的value外還支持hash、set、zset、list等數據結構。
你有嘗試的去優化redis嗎?
rdb和aof工作原理?各有什么優缺點
RDB:是Redis DataBase縮寫快照,RDB是Redis默認的持久化方式。按照一定的時間將內存的數據以快照的形式保存到硬盤中,對應產生的數據文件為dump.rdb。通過配置文件中的save參數來定義快照的周期。
優點:
缺點:
AOF:持久化 是將Redis執行的每次寫命令記錄到單獨的日志文件中,當重啟Redis會重新將持久化的日志中文件恢復數據;當兩種方式同時開啟時,數據恢復Redis會優先選擇AOF恢復。
優點:
缺點:
如何選擇合適的持久化方式
Redis持久化數據和緩存怎么做擴容?
如果Redis被當做緩存使用,使用一致性哈希實現動態擴容縮容。
如果Redis被當做一個持久化存儲使用,必須使用固定的keys-to-nodes映射關系,節點的數量一旦確定不能變化。否則的話(即Redis節點需要動態變化的情況),必須使用可以在運行時進行數據再平衡的一套系統,而當前只有Redis集群可以做到這樣。
Redis 對過期數據的處理
LRU(the least recently used 最近最少使用)算法
如果一個數據在最近沒有被訪問到,那么在未來被訪問的可能性也很小,因此當空間滿的時候,最久沒有被訪問的數據最先被置換(淘汰)
LRU算法通常通過雙向鏈表來實現,添加元素的時候,直接插入表頭,訪問元素的時候,先判斷元素是否在鏈表中存在,如果存在就把該元素移動至表頭
淘汰的時候 把隊尾的一些刪掉;
Redis線程模型
Redis基于Reactor模式開發了網絡事件處理器,這個處理器被稱為文件事件處理器(file event handler)。它的組成結構為4部分:多個套接字、IO多路復用程序、文件事件分派器、事件處理器。因為文件事件分派器隊列的消費是單線程的,所以Redis才叫單線程模型
官方Redis Cluster 方案(服務端路由查詢)
Redis Cluster是一種服務端Sharding技術,3.0版本開始正式提供。Redis Cluster并沒有使用一致性hash,而是采用slot(槽)的概念,一共分成16384個槽。將請求發送到任意節點,接收到請求的節點會將查詢請求發送到正確的節點上執行
方案說明
在 redis cluster 架構下,每個 redis 要放開兩個端口號,比如一個是 6379,另外一個就是 加1w 的端口號,比如 16379。 16379 端口號是用來進行節點間通信的,也就是 cluster bus 的東西,cluster bus 的通信,用來進行故障檢測、配置更新、故障轉移授權。cluster bus 用了另外一種二進制的協議,gossip 協議,用于節點間進行高效的數據交換,占用更少的網絡帶寬和處理時間。
優點:
無中心架構,支持動態擴容,對業務透明
具備Sentinel的監控和自動Failover(故障轉移)能力
客戶端不需要連接集群所有節點,連接集群中任何一個可用節點即可
高性能,客戶端直連redis服務,免去了proxy代理的損耗
缺點:
運維也很復雜,數據遷移需要人工干預
只能使用0號數據庫
不支持批量操作(pipeline管道操作)
Redis的哨兵模式
sentinel,中文名是哨兵。哨兵是 redis 集群機構中非常重要的一個組件,主要有以下功能:
集群監控:負責監控 redis master 和 slave 進程是否正常工作。
消息通知:如果某個 redis 實例有故障,那么哨兵負責發送消息作為報警通知給管理員
故障轉移:如果 master node 掛掉了,會自動轉移到 slave node 上。
配置中心:如果故障轉移發生了,通知 client 客戶端新的 master 地址。
哨兵用于實現 redis 集群的高可用,本身也是分布式的,作為一個哨兵集群去運行,互相協同工作。
故障轉移時,判斷一個 master node 是否宕機了,需要大部分的哨兵都同意才行,涉及到了分布式選舉的問題。
哨兵至少需要 3 個實例,來保證自己的健壯性。
哨兵 + redis 主從的部署架構,是不保證數據零丟失的,只能保證 redis 集群的高可用性。
基于代理服務器分片
例如開源的: Twemproxy Codis
redis的有哪些主從同步方式?
redis集群擴容與收縮
Redis底層ZSet跳表是如何設計與實現的
Redis底層ZSet實現壓縮列表和跳表如何選擇
Redis高并發場景熱點緩存如何重建
高并發場景緩存穿透&失效&雪崩如何解決
Redis集群架構如何抗住雙十一的洪峰流量
Redis緩存與數據庫雙寫不一致如何解決
Redis分布式鎖主從架構鎖失效問題如何解決
從CAP角度解釋下Redis&Zookeeper鎖架構異同
超大并發的分布式鎖架構該如何設計
雙十一億級用戶日活統計如何用Redis快速計算
雙十一電商推薦系統如何用Redis實現
雙十一電商購物車系統如何用Redis實現
類似微信的社交App朋友圈關注模型如何設計實現
美團單車如何基于Redis快速找到附近的車
Redis 6.0 多線程模型比單線程優化在哪里了
Spring
請簡要描述一下IOC 和AOP?
Spring是怎么解決的循環依賴?
三級緩存
三級 啥都還沒有干,給了工廠 singletonFactories : 單例對象工廠的cache
二級,完成了構造函數但是還沒有注意依賴earlySingletonObjects :提前暴光的單例對象的Cache
一級 加載好的beansingletonObjects:單例對象的cache
為啥Spring不能解決“A的構造方法中依賴了B的實例對象
看上面
Spring 中使用的那種方式來實現動態代理的?
Spring中的事務傳播機制?事務嵌套
Spring中同一個類中有方法A 和 B 兩個方法都被標記了@Transtional,在A中調用了B,那么B的事務會生效嗎?為什么?
添加鏈接描述
Spring 中IOC 和工廠模式的區別,為啥不用工廠模式?
工廠模式的升級 IOC 依賴注入 控制反轉
(1)也許有人說,IoC和工廠模式不是一樣的作用嗎,用IoC好象還麻煩一點。 舉個例子,如果用戶需求發生變化,要把Chinese類修改一下。那么前一種工廠模式,就要更改Factory類的方法,并且重新編譯布署。而IoC只需 要將class屬性改變一下,并且由于IoC利用了Java反射機制,這些對象是動態生成的,這時我們就可以熱插撥Chinese對象(不必把原程序停止 下來重新編譯布署,java特性 需要重新編譯)
(2)也許有人說,即然IoC這么好,那么我把系統所有對象都用IoC方式來生成。 注意,IoC的靈活性是有代價的:設置步驟麻煩、生成對象的方式不直觀、反射比正常生成對象在效率上慢一點。因此使用IoC要看有沒有必要,我認為比較通用的判斷方式是:用到工廠模式的地方都可以考慮用IoC模式。
網絡
select、poll、epoll之間的區別
select,poll,epoll都是IO多路復用的機制。I/O多路復用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒后自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。
select: 時間復雜度O(n),它僅僅知道了,有I/O事件發生了,卻并不知道是哪那幾個流(可能有一個,多個,甚至全部) select具有O(n)的無差別輪詢復雜度, 同時處理的流越多,無差別輪詢時間就越長。遍歷 ; 有最大連接數的限制,在FD_SETSIZE宏定義
poll:時間復雜度O(n),poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然后查詢每個fd對應的設備狀態, 但是它沒有最大連接數的限制,原因是它是基于鏈表來存儲的.但是它沒有最大連接數的限制,原因是它是基于鏈表來存儲的
epoll 時間復雜度O(1),epoll可以理解為event poll,epoll會把哪個流發生了怎樣的I/O事件通知我們。所以我們說epoll實際上是事件驅動(每個事件關聯上fd)的 ;雖然連接數有上限,但是很大,1G內存的機器上可以打開10萬左右的連接
epoll的優點:
1、沒有最大并發連接的限制,能打開的FD的上限遠大于1024(1G的內存上能監聽約10萬個端口);
2、效率提升,不是輪詢的方式,不會隨著FD數目的增加效率下降。只有活躍可用的FD才會調用callback函數;
即Epoll最大的優點就在于它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中,Epoll的效率就會遠遠高于select和poll。
3. 內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減少復制開銷。
BIO、NIO、AIO
BIO
Java BIO 就是傳統的 Java IO 編程,同步并阻塞(傳統阻塞型),服務器實現模式為一個連接一個線程,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理,如果這個連接不作任何事情會造成不必要的線程開銷。BIO 方式適用于連接數比較小且固定的架構
NIO
Java NIO 全稱 Java non-blocking IO,NIO 同步非阻塞 有三大核心部分:Channel(管道)、Buffer(緩沖區)、Selector(選擇器)。NIO 以塊的方式處理數據,塊 I/O 的效率比流 I/O 高很多
NIO 是面向緩沖區編程的。數據讀取到了一個它稍微處理的緩沖區,需要時可在緩沖區中前后移動,這就增加了處理過程中的靈活性,使用它可以提供非阻塞的高伸縮性網絡
NIO 和 BIO 對比
NIO比傳統的BIO核心區別就是,NIO采用的是多路復用的IO模型,普通的IO用的是阻塞的IO模型
netty是用select 還是 epoll
在win下用select 在linux下用epoll
Channel 的基本介紹
說說Reactor線程模型的理解
AIO
DK 7 引入了 Asynchronous I/O,即 AIO。在進行 I/O 編程中,通常用到兩種模式:Reactor 和 Proactor 。Java 的 NIO 就是 Reactor,當有事件觸發時,服務器端得到通知,進行相應的處理。
AIO 叫做異步非阻塞的 I/O,引入了異步通道的概念,采用了 Proactor 模式,簡化了程序編寫,有效的請求才會啟動線程,特點就是先由操作系統完成后才通知服務端程序啟動線程去處理,一般用于連接數較多且連接時長較長的應用。
Reactor 單線程
每個客戶端發起連接請求都會交給acceptor,acceptor根據事件類型交給線程handler處理,但是由于在同一線程中,容易產生一個handler阻塞影響其他的情況。
Reactor 多線程
這里使用了單線程進行接收客戶端的連接,采用了NIO的線程池用來處理客戶端對應的IO操作,由于客戶端連接較多,有時會一個線程對應處理多個連接。
Reactor 主從
這里將接收客戶端請求后采用線程池進行處理,服務端用于接收客戶端連接的不再是個1個單獨的NIO線程,而是一個獨立的NIO線程池。Acceptor接收到客戶端TCP連接請求處理完成后(可能包含接入認證等),將新創建的SocketChannel注冊到IO線程池(sub reactor線程池)的某個IO線程上,由它負責SocketChannel的讀寫和編解碼工作。Acceptor線程池僅僅只用于客戶端的登陸、握手和安全認證,一旦鏈路建立成功,就將鏈路注冊到后端subReactor線程池的IO線程上,由IO線程負責后續的IO操作。
TCP三次握手四次揮手
建立連接時,客戶端發送syn包(syn=j)到服務器,并進入SYN_SENT狀態,等待服務器確認;SYN:同步序列編號(Synchronize Sequence Numbers)
服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED(TCP連接成功)狀態,完成三次握手。
四次揮手
四次揮手中TIME_WAIT狀態存在的目的是什么?
TCP是通過什么機制保障可靠性的?
TCP粘包,拆包及解決方法
產生粘包和拆包問題的主要原因是,操作系統在發送TCP數據的時候,底層會有一個緩沖區,例如1024個字節大小,如果一次請求發送的數據量比較小,沒達到緩沖區大小,TCP則會將多個請求合并為同一個請求進行發送,這就形成了粘包問題;如果一次請求發送的數據量比較大,超過了緩沖區大小,TCP就會將其拆分為多次發送,這就是拆包,也就是將一個大的包拆分為多個小包進行發送
操作系統虛擬內存換頁的過程
kafka
Sentinel的簡單原理
流量控制、熔斷降級、系統負載保護
服務隔離機制:線程池隔離或者信號量隔離機制
線程池隔離:每個接口都有自己獨立的線程池維護我們的請求,每個線程池互不影響,就是每個接口有獨立使用一個線程池,缺點:占用服務器內存非常大
信號量隔離:設置允許我們的某個接口有一個閾值的線程數量去處理接口,如果超出改線程數量則拒絕訪問,有點類似服務限流
分布式鎖
數據庫樂觀鎖
數據庫更新某條記錄為加鎖狀態, update 鎖狀態=加鎖 from table where 鎖狀態=沒加鎖; 返回影響行數=0表示被別人加鎖了就不能加了;
Redis分布式鎖
加鎖
主要回答 NX 意思是SET IF NOT EXIST,即當key不存在時,我們進行set操作;若key已經存在,則不做任何操作;
增加requestId,誰加的鎖必須誰解鎖
設置過期時間
解鎖
1.判斷是不是自己加的鎖
2.是的話刪掉鎖
3.使用lua實現上面兩步驟 保持原子性! 否則可能出現刪除別人的鎖的情況;
比如: A判斷了是自己的鎖,然后準備去刪除這個鎖,突然鎖過期了,B這時候成功加鎖了,那么A再執行刪除操作的時候就會刪掉B的鎖
Mysql
本地事務
原子性(Atomicity) 是通過 undo log 來實現的
一致性(Consistency) 是通過 redo log 來實現的
隔離型(Isolation) 是通過 (讀寫鎖+MVCC)來實現的
持久性(Durability)
隔離型(Isolation) 主要由MVCC和鎖實現
READ UNCOMMITED (未提交讀)
READ COMMITED (提交讀)
REPEATABLE READ (可重復讀)
SERIALIZABLE (可重復讀)
Mysql 默認隔離級別是REPEATABLE READ (可重復讀); 但是他存在幻讀的問題;也就是讀取范圍記錄的時候,可能有其他事物插入了數據導致讀取的不一致;
但是InnoDB解決了幻讀問題; 通過MVCC 多版本并發控制解決了幻讀問題; 具體是通過加鎖,Next-Key Lock :行鎖和間隙鎖組合起來就叫Next-Key Lock。
MVCC 多版本并發控制
InnoBD通過在每行的后面包車2個隱藏列實現,一個保存行的創建事件,一個是過期時間(或刪除時間),當然存儲的并不是時機的時間,而是系統版本號; 每開一個新事務,系統版本號都會自增;
回滾日志 undo log
存在內存中的數據;用于記錄數據被修改前的信息,為了在發生錯誤時回滾之前的操作,需要將之前的操作都記錄下來,然后在發生錯誤時才可以回滾。
事務日志 REDO LOG Write Ahead Log(WAL)策略
Write Ahead Log(WAL)策略 先寫日志
mysql 為了提升性能不會把每次的修改都實時同步到磁盤,而是會先存到Boffer Pool(緩沖池)里頭,把這個當作緩存來用。然后使用后臺線程去做緩沖池和磁盤之間的同步。
那么問題來了,如果還沒來的同步的時候宕機或斷電了怎么辦?
所以引入了redo log來記錄已成功提交事務的修改信息,并且會把redo log持久化到磁盤,系統重啟之后在讀取redo log恢復最新數據。
總結: redo log是用來恢復數據的 用于保障,已提交事務的持久化特性
既然 redo log也要刷盤 為什么不直接刷修改的數據到磁盤呢?
鎖
行鎖(Record Lock):鎖直接加在索引記錄上面,鎖住的是key。
間隙鎖(Gap Lock):鎖定索引記錄間隙,確保索引記錄的間隙不變。間隙鎖是針對事務隔離級別為可重復讀或以上級別而已的。
Next-Key Lock :行鎖和間隙鎖組合起來就叫Next-Key Lock。
索引
索引類型
哈希索引: 基于哈希表的實現
哈希索引質保函哈希值和行指針,不存儲字段
不是順序存儲,無法排序
訪問哈希索引的數據非常快
哈希沖突多的話,一些索引維護操作代價也會很高
InnoDB有一個特殊功能叫 自適應哈希索引,當InnoDb注意到某些索引值被使用非常頻繁,它會在內存中基于B-TREE索引之上在創建一個哈希索引; 完全是自動行為用戶無法控制
高性能索引的一些策略
1.索引列不能是表達式的一部分,也不能是函數的參數
例如: 下面索引會失效,
2.前綴索引.當字段里有很長字符串的列(TEXT,長的VARCHER等…),在前幾個字符串里加索引,這就是前綴索引。
alter table table_name add key(column_name(length));什么是聯合索引,為什么建議盡量用聯合索引
為多列字段建立一個索引,稱之為聯合索引,聯合索引需要遵從最左前綴原則
多個單列索引在多條件查詢時優化器會選擇最優索引策略,可能只用一個索引,也可能將多個索引全用上! 但多個單列索引底層會建立多個B+索引樹,比較占用空間,也會浪費一定搜索效率,所以索引建議最好建聯合索引
什么是覆蓋索引,以及優點?
覆蓋索引:一個輔助索引包含了查詢結果的數據就叫做覆蓋索引,即從輔助索引中就可以得到查詢結果,而不需要從聚集索引中查詢
為什么使用B+樹,而不用其他的例如紅黑樹做索引
InnoDB 與 MyISAM 結構上的區別
MyISAM
InnoDB
葉子節點都保存著完整的數據
什么是索引下推
MySQL之 索引下推
索引條件下推(Index Condition Pushdown),簡稱ICP。MySQL5.6新添加,用于優化數據的查詢。
當你不使用ICP,通過使用非主鍵索引(普通索引or二級索引)進行查詢,存儲引擎通過索引檢索數據,然后返回給MySQL服務器,服務器再判斷是否符合條件。
使用ICP,當存在索引的列做為判斷條件時,MySQL服務器將這一部分判斷條件傳遞給存儲引擎,然后存儲引擎通過判斷索引是否符合MySQL服務器傳遞的條件,只有當索引符合條件時才會將數據檢索出來返回給MySQL服務器。
分區分表分庫
分區: 一張大表進行分區后,他還是一張表,不會變成二張表,但是他存放數據的區塊變多了;突破磁盤I/O瓶頸,想提高磁盤的讀寫能力
分表: 多個表;單表的并發能力提高了,磁盤I/O性能也提高了
分區和分表的測重點不同,分表重點是存取數據時,如何提高mysql并發能力上;而分區呢,如何突破磁盤的讀寫能力,從而達到提高mysql性能的目的。
分庫: 單機性能不夠,分成多個庫提升性能;
分表分庫:
垂直切分,即將表按照功能模塊、關系密切程度劃分出來,部署到不同的庫上水平切分,當一個表中的數據量過大時,我們可以把該表的數據按照某種規則
項目介紹
秒殺系統
但是上面的會導致DB過載,所有的流量都去DB了不合適,所以再提出解決方案
你做的這個秒殺系統QPS怎么樣
你怎么判斷需要多少機器來承受?
如果判斷一個機器達到了極限?CPU IO 磁盤?
CPU
內存使用率
磁盤IO
緩存穿透 緩存擊穿 緩存雪崩
解決方案: ①.數據庫中也沒有的數據,這是可以將k-v對寫成k-null,緩存的時機可以設置短一點例如20秒;可以防止請求反復攻擊同一個id;
解決方案:①.像秒殺場景先緩存預熱,別讓秒殺一瞬間很多請求穿透到了DB;
②.設置熱點數據永不過期
解決方案: ①.過期時間設置隨機,防止同一時間大量數據過期
漏桶算法和令牌桶算法
漏桶算法:能強行限制數據的傳輸速率;水(請求)先進入到漏桶里,漏桶以一定的速度出水,當水流入速度過大會直接溢出,如果要讓自己的系統不被打垮,用令牌桶
令牌桶算法:的原理是系統會以一個恒定的速度往桶里放入令牌,而如果請求需要被處理,則需要先從桶里獲取一個令牌,當桶里沒有令牌可取時,則拒絕服務。如果保證別人的系統不被打垮,用漏桶算法
限流工具類RateLimiter
操作系統
什么是零拷貝
零拷貝指的是,從一個存儲區域到另一個存儲區域的copy任務沒有CPU參與 ;
零拷貝通常用于網絡文件傳輸,以減少CPU消耗和內存帶寬占用,減少用戶空間(用戶可以操作的內存緩存區域)與CPU內核空間(CPU可以操作的內存緩存區域及寄存器)的拷貝過程,減少用戶上下文(用戶狀態環境)與CPU內核上下文(CPU內核狀態環境)間的切換,提高系統效率
什么是 DMA (DMA控制器Direct Memory Access)
直接內存存取; 我們知道 ,硬件和軟件之間的數據傳輸可以通過使用 DMA 來進行,DMA 進行數據傳輸的過程中幾乎不需要 CPU 參與,這樣就可以把 CPU 解放出來去做更多其他的事情; 但是當數據需要在用戶地址空間的緩沖區和 Linux 操作系統內核的頁緩存之間進行傳輸的時候,并沒有類似 DMA 這種工具可以使用
傳統拷貝方式
發生4次空間切換(1、4、5、7),發生4次copy(3、4、5、6),其中有2次CPU(4、5)參與
利用 mmap()實現零拷貝 應用程序跟操作系統共享這個緩沖區(地址映射) 用戶空間可以修改數據
Memory Mapped Files :簡稱mmap,簡單描述其作用就是:將磁盤文件映射到內存, 用戶通過修改內存就能修改磁盤文件。
調用mmap來替代read()來減少拷貝次數;應用程序調用了 mmap() 之后,數據會先通過 DMA 拷貝到操作系統內核的緩沖區中去。接著,應用程序跟操作系統共享這個緩沖區,這樣,操作系統內核和應用程序存儲空間就不需要再進行任何的數據拷貝操作。應用程序調用了 write() 之后,操作系統內核將數據從原來的內核緩沖區中拷貝到與 socket 相關的內核緩沖區中。接下來,數據從內核 socket 緩沖區拷貝到協議引擎中去,這是第三次數據拷貝操作。
缺點: 其他的進程截斷
使用 mma()p 其實是存在潛在的問題的。當對文件進行了內存映射,然后調用 write() 系統調用,如果此時其他的進程截斷了這個文件,那么 write() 系統調用將會被總線錯誤信號 SIGBUS 中斷,因為此時正在執行的是一個錯誤的存儲訪問。這個信號將會導致進程被殺死
解決: 可以通過內核對文件加讀或者寫的租借鎖
第二種方法是通過文件租借鎖來解決這個問題的,這種方法相對來說更好一些。我們可以通過內核對文件加讀或者寫的租借鎖,當另外一個進程嘗試對用戶正在進行傳輸的文件進行截斷的時候,內核會發送給用戶一個實時信號:RT_SIGNAL_LEASE 信號,這個信號會告訴用戶內核破壞了用戶加在那個文件上的寫或者讀租借鎖,那么 write() 系統調用則會被中斷,并且進程會被 SIGBUS 信號殺死,返回值則是中斷前寫的字節數,errno 也會被設置為 success。文件租借鎖需要在對文件進行內存映射之前設置
sendfile() 方式零拷貝 不僅減少了數據拷貝操作,它也減少了上下文切換 但是用戶空間不可修改數據
為了簡化用戶接口,同時還要繼續保留 mmap()/write() 技術的優點:減少 CPU 的拷貝次數,Linux 在版本 2.1 中引入了 sendfile() 這個系統調用。
sendfile() 系統調用不需要將數據拷貝或者映射到應用程序地址空間中去,所以 sendfile() 只是適用于應用程序地址空間不需要對所訪問數據進行處理的情況
局限性:
帶有 DMA 收集拷貝功能的 sendfile() 不拷貝內容,只拷貝描述符(帶地址和偏移量)
為啥叫 收集拷貝? 待傳輸的數據可以分散在存儲的不同位置上,而不需要在連續存儲中存放。這樣一來,從文件中讀出的數據就根本不需要被拷貝到 socket 緩沖區中去,而只是需要將緩沖區描述符傳到網絡協議棧中去,之后其在緩沖區中建立起數據包的相關結構,然后通過 DMA 收集拷貝功能將所有的數據結合成一個網絡數據包
利用寫時復制
寫時復制是計算機編程中的一種優化策略,它的基本思想是這樣的:如果有多個應用程序需要同時訪問同一塊數據,那么可以為這些應用程序分配指向這塊數據的指針,在每一個應用程序看來,它們都擁有這塊數據的一份數據拷貝,當其中一個應用程序需要對自己的這份數據拷貝進行修改的時候,就需要將數據真正地拷貝到該應用程序的地址空間中去,也就是說,該應用程序擁有了一份真正的私有數據拷貝,這樣做是為了避免該應用程序對這塊數據做的更改被其他應用程序看到。這個過程對于應用程序來說是透明的,如果應用程序永遠不會對所訪問的這塊數據進行任何更改,那么就永遠不需要將數據拷貝到應用程序自己的地址空間中去。這也是寫時復制的最主要的優點。
Linux 中的零拷貝技術
零拷貝
Java NIO對文件映射mmap的支持
Java NIO,提供了一個 MappedByteBuffer 類可以用來實現內存映射。
Java NIO對sendfile的支持就是
FileChannel.transferTo()/transferFrom()。
Spring
Spring cloud
https://blog.csdn.net/qq_42046105/article/details/83793787
開放性問題
你覺得一個好的團隊應該是什么樣的?
解決問題的能力
平時會使用設計模式嗎?請講一個使用的情景
工作中有碰到過線上的問題嗎?怎么解決的?
你有過Jvm調優經驗嗎?
你做了這么多系統,是怎么做系統監控的,系統的穩定性?內存滿了,磁盤滿了,和cpu高了之類的?
有碰到過數據庫死鎖的問題嗎?怎么解決的?
算法
滑動窗口
反轉單鏈表
復雜鏈表復制
數組a,先單調地址再單調遞減,輸出數組中不同元素個數。要求:O(1)空間復雜度,不能改變原數組
給出兩個升序數組A、B和長度m、n,求第k個大的數
給出數組A,長度為n,數組中元素的值位于[0, n - 1]之間,求是否有重復元素
鏡像二叉樹
二叉樹多個節點的最近公共祖先
樹的非遞歸先序遍歷。
總結
- 上一篇: 存储过程 while is null_4
- 下一篇: Pycharm安装包(类库)的方法总结及