日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JAVA面试八股文

發布時間:2023/12/20 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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進行深度克隆; 先將對象序列化;然后再反序列化;

public static <T extends Serializable> T deepClone(T t) throws CloneNotSupportedException {// 保存對象為字節數組try {ByteArrayOutputStream bout = new ByteArrayOutputStream();try(ObjectOutputStream out = new ObjectOutputStream(bout)) {out.writeObject(t);}// 從字節數組中讀取克隆對象try(InputStream bin = new ByteArrayInputStream(bout.toByteArray())) {ObjectInputStream in = new ObjectInputStream(bin);return (T)(in.readObject());}}catch (IOException | ClassNotFoundException e){CloneNotSupportedException cloneNotSupportedException = new CloneNotSupportedException();e.initCause(cloneNotSupportedException);throw cloneNotSupportedException;}}

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。

  • 使用static的ThreadLocal,延長了ThreadLocal的生命周期,可能導致的內存泄漏
  • 分配使用了ThreadLocal又不再調用get(),set(),remove()方法,那么就會導致內存泄漏。
  • 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中的區別

  • JDK1.7用的是頭插法,而JDK1.8及之后使用的都是尾插法,那么他們為什么要這樣做呢?因為JDK1.7是用單鏈表進行的縱向延伸,當采用頭插法就是能夠提高插入的效率,但是也會容易出現逆序且環形鏈表死循環問題。但是在JDK1.8之后是因為加入了紅黑樹使用尾插法,能夠避免出現逆序且鏈表死循環的問題。
  • 擴容后數據存儲位置的計算方式也不一樣:1. 在JDK1.7的時候是直接用hash值和需要擴容的二進制數進行&(這里就是為什么擴容的時候為啥一定必須是2的多少次冪的原因所在,因為如果只有2的n次冪的情況時最后一位二進制數才一定是1,這樣能最大程度減少hash碰撞)(hash值 & length-1)
    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+紅黑樹,相對而言

  • JDK1.8的實現降低鎖的粒度,JDK1.7版本鎖的粒度是基于Segment的,包含多個HashEntry,而JDK1.8鎖的粒度就是HashEntry(首節點)
  • JDK1.8版本的數據結構變得更加簡單,使得操作也更加清晰流暢,因為已經使用synchronized來進行同步,所以不需要分段鎖的概念,也就不需要Segment這種數據結構了,由于粒度的降低,實現的復雜度也增加了
  • JDK1.8使用紅黑樹來優化鏈表,基于長度很長的鏈表的遍歷是一個很漫長的過程,而紅黑樹的遍歷效率是很快的,代替一定閾值的鏈表,這樣形成一個最佳拍檔
  • JDK1.8為什么使用內置鎖synchronized來代替重入鎖ReentrantLock; 因為粒度降低了
  • 提到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的使用比較方便簡潔,并且由編譯器去保證鎖的加鎖和釋放

  • 代碼塊同步: 通過使用monitorenter和monitorexit指令實現的.
  • 同步方法: ACC_SYNCHRONIZED修飾
  • ReentrantLock

    ReenTrantLock的實現是一種自旋鎖,通過循環調用CAS操作來實現加鎖。它的性能比較好也是因為避免了使線程進入內核態的阻塞狀態。

  • 等待可中斷,持有鎖的線程長期不釋放的時候,正在等待的線程可以選擇放棄等待,這相當于Synchronized來說可以避免出現死鎖的情況。通過lock.lockInterruptibly()來實現這個機制。
  • 公平鎖,多個線程等待同一個鎖時,必須按照申請鎖的時間順序獲得鎖,Synchronized鎖非公平鎖,ReentrantLock默認的構造函數是創建的非公平鎖,可以通過參數true設為公平鎖,但公平鎖表現的性能不是很好。
  • 鎖綁定多個條件,一個ReentrantLock對象可以同時綁定對個對象。ReenTrantLock提供了一個Condition(條件)類,用來實現分組喚醒需要喚醒的線程們,而不是像synchronized要么隨機喚醒一個線程要么喚醒全部線程。
  • 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 于該線程中的任意后續操作。
  • 監視器鎖規則:對一個監視器鎖的解鎖,happens- before 于隨后對這個監視器鎖的加鎖。
  • volatile 變量規則:對一個 volatile 域的寫,happens- before 于任意后續對這個 volatile 域的讀。
  • 傳遞性:如果 A happens- before B,且 B happens- before C,那么 A happens- before C。
  • 注意,兩個操作之間具有 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堆和方法區則不一樣,我們只有在程序運行期間才知道會創建哪些對象,所以這部分內存的分配和回收都是動態的。一般我們所說的垃圾回收也是針對的這一部分。

    垃圾檢查有哪些算法?

  • 引用計數法 :給一個對象添加引用計數器,每當有個地方引用它,計數器就加1;引用失效就減1。
    好了,問題來了,如果我有兩個對象A和B,互相引用,除此之外,沒有其他任何對象引用它們,實際上這兩個對象已經無法訪問,即是我們說的垃圾對象。但是互相引用,計數不為0,導致無法回收,所以還有另一種方法:
  • 可達性分析算法:以根集對象為起始點進行搜索,如果有對象不可達的話,即是垃圾對象。這里的根集一般包括java棧中引用的對象、方法區常良池中引用的對象
  • 垃圾回收方法有哪些?

  • 標記-清除(Mark-sweep):標記清除算法分為兩個階段,標記階段和清除階段。標記階段任務是標記出所有需要回收的對象,清除階段就是清除被標記對象的空間。優缺點:實現簡單,容易產生內存碎片
  • 復制(Copying)將可用內存劃分為大小相等的兩塊,每次只使用其中的一塊。當進行垃圾回收的時候了,把其中存活對象全部復制到另外一塊中,然后把已使用的內存空間一次清空掉。 優缺點:不容易產生內存碎片;可用內存空間少;存活對象多的話,效率低下。
  • 標記-整理(Mark-Compact)先標記存活對象,然后把存活對象向一邊移動,然后清理掉端邊界以外的內存 優缺點:不容易產生內存碎片;內存利用率高;存活對象多并且分散的時候,移動次數多,效率低下
  • 分代收集算法(目前大部分JVM的垃圾收集器所采用的算法)
    年輕代(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算法 也就是根; 只要看這個兩個對象有沒有掛在 根 上, 掛在根上了 就不會被回收; 沒有掛在根上就會回收;

    哪些可以算作根節點?

  • 方法區中的靜態屬性
  • 方法區的中的常量
  • 虛擬機中的局部變量
  • 本地方法棧中JNI
  • 垃圾收集器 G1有什么樣的特性了解嗎? CMS呢?

    Cms與G1的優缺點

    CMS垃圾回收器:

  • 初始標記(CMS-initial-mark) ,會導致STW(stop-the-world);
  • 并發標記(CMS-concurrent-mark),與用戶線程同時運行
  • 預清理(CMS-concurrent-preclean),與用戶線程同時運行
  • 可被終止的預清理(CMS-concurrent-abortable-preclean) 與用戶線程同時運行;
  • 重新標記(CMS-remark) ,會導致STW; 這個階段會導致第二次stop the word,該階段的任務是完成標記整個年老代的所有的存活對象。
    這個階段,重新標記的內存范圍是整個堆,包含_young_gen和_old_gen。為什么要掃描新生代呢,因為對于老年代中的對象,如果被新生代中的對象引用,那么就會被視為存活對象,即使新生代的對象已經不可達了
  • 并發清除(CMS-concurrent-sweep),與用戶線程同時運行;
  • 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文件

    類加載的過程

  • 類加載的生命周期:加載(Loading)–>驗證(Verification)–>準備(Preparation)–>解析(Resolution)–>初始化(Initialization)–>使用(Using)–>卸載(Unloading)
  • 加載

    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);

    編寫自定義類加載器時,一般有哪些注意點?

  • 一般盡量不要覆寫已有的loadClass(…)方法中的委派邏輯; 這樣做極有可能引起系統默認的類加載器不能正常工作
  • 如何在運行時判斷系統類加載器能加載哪些路徑下的類?

    一 是可以直接調用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任務隊列

  • 直接提交隊列:設置為SynchronousQueue隊列,提交的任務不會被保存,總是會馬上提交執行
  • 有界的任務隊列:有界的任務隊列可以使用ArrayBlockingQueue實現
  • 無界的任務隊列:有界任務隊列可以使用LinkedBlockingQueue實現
  • 優先任務隊列:優先任務隊列通過PriorityBlockingQueue實現,它其實是一個特殊的無界隊列,PriorityBlockingQueue隊列可以自定義規則根據任務的優先級順序先后執行
  • 線程設置越多越好嗎?設置到什么值比較合理?

    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請求流程

  • client一個線程調用遠程接口,生成一個唯一的ID(比如一段隨機字符串,UUID等),Dubbo是使用AtomicLong從0開始累計數字的
  • 將打包的方法調用信息(如調用的接口名稱,方法名稱,參數值列表等),和處理結果的回調對象callback,全部封裝在一起,組成一個對象object
  • 向專門存放調用信息的全局ConcurrentHashMap里面put(ID, object)
  • 將ID和打包的方法調用信息封裝成一對象connRequest,使用IoSession.write(connRequest)異步發送出去
  • 當前線程再使用callback的get()方法試圖獲取遠程返回的結果,在get()內部,則使用synchronized獲取回調對象callback的鎖, 再先檢測是否已經獲取到結果,如果沒有,然后調用callback的wait()方法,釋放callback上的鎖,讓當前線程處于等待狀態。
  • 服務端接收到請求并處理后,將結果(此結果中包含了前面的ID,即回傳)發送給客戶端,客戶端socket連接上專門監聽消息的線程收到消息,分析結果,取到ID,再從前面的ConcurrentHashMap里面get(ID),從而找到callback,將方法調用結果設置到callback對象里。
  • 監聽線程接著使用synchronized獲取回調對象callback的鎖(因為前面調用過wait(),那個線程已釋放callback的鎖了),再notifyAll(),喚醒前面處于等待狀態的線程繼續執行(callback的get()方法繼續執行就能拿到調用結果了),至此,整個過程結束。
  • 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 原生自帶的服務降級功能
  • 引入支持服務降級的組件 比如 Alibaba Sentinel
  • Dubbo 如何做限流?

  • Dubbo 原生自帶的限流功能,通過 TpsLimitFilter 實現,僅適用于服務提供者
  • 引入支持限流的組件 例如Sentine
  • 如何自己設計一個類似 Dubbo 的 RPC 框架?

    舉個栗子,我給大家說個最簡單的回答思路:

  • 上來你的服務就得去注冊中心注冊吧,你是不是得有個注冊中心,保留各個服務的信心,可以用 zookeeper 來做,對吧。
  • 然后你的消費者需要去注冊中心拿對應的服務信息吧,對吧,而且每個服務可能會存在于多臺機器上。
  • 接著你就該發起一次請求了,咋發起?當然是基于動態代理了,你面向接口獲取到一個動態代理,這個動態代理就是接口在本地的一個代理,然后這個代理會找到服務對應的機器地址。
  • 然后找哪個機器發送請求?那肯定得有個負載均衡算法了,比如最簡單的可以隨機輪詢是不是。
  • 接著找到一臺機器,就可以跟它發送請求了,第一個問題咋發送?你可以說用 netty 了,nio 方式;第二個問題發送啥格式數據?你可以說用 hessian 序列化協議了,或者是別的,對吧。然后請求過去了。
  • 服務器那邊一樣的,需要針對你自己的服務生成一個動態代理,監聽某個網絡端口了,然后代理你本地的服務代碼。接收到請求的時候,就調用對應的服務代碼,對吧。
    這就是一個最最基本的 rpc 框架的思路,先不說你有多牛逼的技術功底,哪怕這個最簡單的思路你先給出來行不行?
  • dubbo SPI 機制 與 JAVA SPI?

    這里寫鏈接內容

    Zookeeper

    zookeeper快速選舉描述一下?

  • 每個事務,會分配全局唯一的遞增id(zxid,64位:epoch + 自增 id),每次一個leader被選出來,它都會有一 個新的epoch,標識當前屬于那個leader的統治時期。低32位用于遞增計數
  • 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創建一個全局的路徑,這個路徑就可以作為一個名字,指向集群中的集群,提供的服務的地址,或者一個遠程的對象等等。
  • 配置管理
  • 集群管理: 所謂集群管理就是:是否有機器退出和加入、選舉master。
  • 分布式鎖
  • 分布式隊列:生產者通過在queue節點下創建順序節點來存放數據,消費者通過讀取順序節點來消費數據。
  • zookeeper實現分布式鎖怎么實現?

  • 創建臨時順序節點
  • 判斷自己是不是最小值,是則獲取了鎖
  • 用watch自己前面的一個節點;如果前面的節點刪除了,則節點收到通知之后,立馬判斷自己是不是最小的節點,如果是則獲取鎖;如果不是,則watch它前面的一個節點
    每個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 中的數據結構是專門進行設計的;
  • 采用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的性能消耗;(但是redis6.0已經開始使用多線程了,不過是在網絡層面)
  • 使用多路 I/O 復用模型,非阻塞 IO;
  • redis的持久化策略?

    Redis 提供兩種持久化機制 RDB(默認) 和 AOF 機制:

    redis有哪些數據類型?

    數據結構豐富,除了支持string類型的value外還支持hash、set、zset、list等數據結構。

    你有嘗試的去優化redis嗎?

    rdb和aof工作原理?各有什么優缺點

    RDB:是Redis DataBase縮寫快照,RDB是Redis默認的持久化方式。按照一定的時間將內存的數據以快照的形式保存到硬盤中,對應產生的數據文件為dump.rdb。通過配置文件中的save參數來定義快照的周期。
    優點:

  • 只有一個文件 dump.rdb,方便持久化。
  • 容災性好,一個文件可以保存到安全的磁盤。
  • 性能最大化,fork 子進程來完成寫操作,讓主進程繼續處理命令,所以是 IO 最大化。使用單獨子進程來進行持久化,主進程不會進行任何 IO 操作,保證了 redis 的高性能
  • 相對于數據集大時,比 AOF 的啟動效率更高。
    缺點:
  • 數據安全性低。RDB 是間隔一段時間進行持久化,如果持久化之間 redis 發生故障,會發生數據丟失。所以這種方式更適合數據要求不嚴謹的時候)
  • AOF:持久化 是將Redis執行的每次寫命令記錄到單獨的日志文件中,當重啟Redis會重新將持久化的日志中文件恢復數據;當兩種方式同時開啟時,數據恢復Redis會優先選擇AOF恢復。
    優點:

  • 數據安全,aof 持久化可以配置 appendfsync 屬性,有 always,每進行一次 命令操作就記錄到 aof 文件中一次。
  • 通過 append 模式寫文件,即使中途服務器宕機,可以通過 redis-check-aof 工具解決數據一致性問題。
  • AOF 機制的 rewrite 模式。AOF 文件沒被 rewrite 之前(文件過大時會對命令 進行合并重寫),可以刪除其中的某些命令(比如誤操作的 flushall))
    缺點:
  • AOF 文件比 RDB 文件大,且恢復速度慢。
  • 數據集大的時候,比 rdb 啟動效率低。
  • 如何選擇合適的持久化方式

  • 如果需要達到很高的數據安全性,應該同時使用兩種持久化功能。在這種情況下,當 Redis 重啟的時候會優先載入AOF文件來恢復原始的數據,因為在通常情況下AOF文件保存的數據集要比RDB文件保存的數據集要完整。
  • 如果你非常關心你的數據, 但仍然可以承受數分鐘以內的數據丟失,那么你可以只使用RDB持久化。
  • 有很多用戶都只使用AOF持久化,但并不推薦這種方式,因為定時生成RDB快照(snapshot)非常便于進行數據庫備份, 并且 RDB 恢復數據集的速度也要比AOF恢復的速度要快,除此之外,使用RDB還可以避免AOF程序的bug。
  • Redis持久化數據和緩存怎么做擴容?

    如果Redis被當做緩存使用,使用一致性哈希實現動態擴容縮容。
    如果Redis被當做一個持久化存儲使用,必須使用固定的keys-to-nodes映射關系,節點的數量一旦確定不能變化。否則的話(即Redis節點需要動態變化的情況),必須使用可以在運行時進行數據再平衡的一套系統,而當前只有Redis集群可以做到這樣。

    Redis 對過期數據的處理

  • 惰性刪除:惰性刪除不會去主動刪除數據,而是在訪問數據的時候,再檢查當前鍵值是否過期,如果過期則執行刪除并返回 null 給客戶端,如果沒有過期則返回正常信息給客戶端。
  • 定期刪除:Redis會周期性的隨機測試一批設置了過期時間的key并進行處理。測試到的已過期的key將被刪除。
  • 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個槽。將請求發送到任意節點,接收到請求的節點會將查詢請求發送到正確的節點上執行
    方案說明

  • 通過哈希的方式,將數據分片,每個節點均分存儲一定哈希槽(哈希值)區間的數據,默認分配了16384 個槽位
  • 每份數據分片會存儲在多個互為主從的多節點上
  • 數據寫入先寫主節點,再同步到從節點(支持配置為阻塞同步)
  • 同一分片多個節點間的數據不保持一致性
  • 讀取數據時,當客戶端操作的key沒有分配在該節點上時,redis會返回轉向指令,指向正確的節點
  • 擴容時時需要需要把舊節點的數據遷移一部分到新節點
  • 在 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 對比

  • BIO 以流的方式處理數據,而 NIO 以塊的方式處理數據,塊 I/O 的效率比流 I/O 高很多。
  • BIO 是阻塞的,而 NIO 是非阻塞的。
  • BIO 基于字節流和字符流進行操作,而 NIO 基于 Channel(通道)和 Buffer(緩沖區)進行操作,數據總是從通道讀取到緩沖區中,或者從緩沖區寫入到通道中。Selector(選擇器)用于監聽多個通道事件(比如連接請求,數據到達等),因此使用單個線程就可以監聽多個客戶端通道。
    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就會將其拆分為多次發送,這就是拆包,也就是將一個大的包拆分為多個小包進行發送

  • 發送端給每個數據包添加包首部,首部中應該至少包含數據包的長度,這樣接收端在接收到數據后,通過讀取包首部的長度字段,便知道每一個數據包的實際長度了。
  • 發送端將每個數據包封裝為固定長度(不夠的可以通過補0填充),這樣接收端每次從接收緩沖區中讀取固定長度的數據就自然而然的把每個數據包拆分開來。
  • 可以在數據包之間設置邊界,如添加特殊符號,這樣,接收端通過這個邊界就可以將不同的數據包拆分開。
  • 操作系統虛擬內存換頁的過程

    kafka

    Sentinel的簡單原理

    流量控制、熔斷降級、系統負載保護

    服務隔離機制:線程池隔離或者信號量隔離機制

    線程池隔離:每個接口都有自己獨立的線程池維護我們的請求,每個線程池互不影響,就是每個接口有獨立使用一個線程池,缺點:占用服務器內存非常大
    信號量隔離:設置允許我們的某個接口有一個閾值的線程數量去處理接口,如果超出改線程數量則拒絕訪問,有點類似服務限流

    分布式鎖

    數據庫樂觀鎖

    數據庫更新某條記錄為加鎖狀態, update 鎖狀態=加鎖 from table where 鎖狀態=沒加鎖; 返回影響行數=0表示被別人加鎖了就不能加了;

    Redis分布式鎖

  • 互斥性。在任意時刻,只有一個客戶端能持有鎖。
  • 不會發生死鎖。即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證后續其他客戶端能加鎖。
  • 具有容錯性。只要大部分的Redis節點正常運行,客戶端就可以加鎖和解鎖。
  • 解鈴還須系鈴人。加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了。
  • 加鎖

    主要回答 NX 意思是SET IF NOT EXIST,即當key不存在時,我們進行set操作;若key已經存在,則不做任何操作;
    增加requestId,誰加的鎖必須誰解鎖
    設置過期時間

    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);if (LOCK_SUCCESS.equals(result)) {return true;}return false;}

    解鎖

    1.判斷是不是自己加的鎖
    2.是的話刪掉鎖
    3.使用lua實現上面兩步驟 保持原子性! 否則可能出現刪除別人的鎖的情況;
    比如: A判斷了是自己的鎖,然后準備去刪除這個鎖,突然鎖過期了,B這時候成功加鎖了,那么A再執行刪除操作的時候就會刪掉B的鎖

    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));if (RELEASE_SUCCESS.equals(result)) {return true;}return false;}

    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也要刷盤 為什么不直接刷修改的數據到磁盤呢?

  • redo_log 存儲的是順序刷盤,而修改數據的刷盤是隨機I/O; 前者更快
  • r組提交 Group Commit,redo log 和 binlog 都具有組提交特性,在刷盤時通過等待一段時間來收集多個事務日志同時進行刷盤
  • 行鎖(Record Lock):鎖直接加在索引記錄上面,鎖住的是key。
    間隙鎖(Gap Lock):鎖定索引記錄間隙,確保索引記錄的間隙不變。間隙鎖是針對事務隔離級別為可重復讀或以上級別而已的。
    Next-Key Lock :行鎖和間隙鎖組合起來就叫Next-Key Lock。

    索引

    索引類型

  • B+索引
  • 哈希索引
  • 空間數據索引
  • 全文索引
  • 哈希索引: 基于哈希表的實現

    哈希索引質保函哈希值和行指針,不存儲字段
    不是順序存儲,無法排序
    訪問哈希索引的數據非常快
    哈希沖突多的話,一些索引維護操作代價也會很高
    InnoDB有一個特殊功能叫 自適應哈希索引,當InnoDb注意到某些索引值被使用非常頻繁,它會在內存中基于B-TREE索引之上在創建一個哈希索引; 完全是自動行為用戶無法控制

    高性能索引的一些策略

    1.索引列不能是表達式的一部分,也不能是函數的參數
    例如: 下面索引會失效,

    select a from table where actor_id +1 = 5;

    2.前綴索引.當字段里有很長字符串的列(TEXT,長的VARCHER等…),在前幾個字符串里加索引,這就是前綴索引。

    alter table table_name add key(column_name(length));

    什么是聯合索引,為什么建議盡量用聯合索引

    為多列字段建立一個索引,稱之為聯合索引,聯合索引需要遵從最左前綴原則
    多個單列索引在多條件查詢時優化器會選擇最優索引策略,可能只用一個索引,也可能將多個索引全用上! 但多個單列索引底層會建立多個B+索引樹,比較占用空間,也會浪費一定搜索效率,所以索引建議最好建聯合索引

    什么是覆蓋索引,以及優點?

    覆蓋索引:一個輔助索引包含了查詢結果的數據就叫做覆蓋索引,即從輔助索引中就可以得到查詢結果,而不需要從聚集索引中查詢

    為什么使用B+樹,而不用其他的例如紅黑樹做索引

  • 文件很大,不可能全部存儲在內存中,故要存儲到磁盤上
  • 索引的結構組織要盡量減少查找過程中磁盤I/O的存取次數(為什么使用B-/+Tree,還跟磁盤存取原理有關。)
  • 局部性原理與磁盤預讀,預讀的長度一般為頁(page)的整倍數,(在許多操作系統中,頁得大小通常為4k)
  • 數據庫系統巧妙利用了磁盤預讀原理,將一個節點的大小設為等于一個頁,默認16k,這樣每個節點只需要一次I/O就可以完全載入,(由于節點中有兩個數組,所以地址連續)。而紅黑樹這種結構,h明顯要深的多。由于邏輯上很近的節點(父子)物理上可能很遠,無法利用局部性
  • InnoDB 與 MyISAM 結構上的區別

  • InnoDB的主鍵索引 ,MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引,所以必須有主鍵,如果沒有顯示定義,自動為生成一個隱含字段作為主鍵,這個字段長度為6個字節,類型為長整形
  • InnoDB的輔助索引(Secondary Index, 也就是非主鍵索引)也會包含主鍵列,比如名字建立索引,內部節點 會包含名字,葉子節點會包含該名字對應的主鍵的值,如果主鍵定義的比較大,其他索引也將很
  • MyISAM引擎使用B+Tree作為索引結構,索引文件葉節點的data域存放的是數據記錄的地址,指向數據文件中對應的值,每個節點只有該索引列的值
  • MyISAM主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是唯一的,輔助索引可以重復
  • MyISAM

    InnoDB

    葉子節點都保存著完整的數據

    什么是索引下推

    MySQL之 索引下推

    索引條件下推(Index Condition Pushdown),簡稱ICP。MySQL5.6新添加,用于優化數據的查詢。
    當你不使用ICP,通過使用非主鍵索引(普通索引or二級索引)進行查詢,存儲引擎通過索引檢索數據,然后返回給MySQL服務器,服務器再判斷是否符合條件。
    使用ICP,當存在索引的列做為判斷條件時,MySQL服務器將這一部分判斷條件傳遞給存儲引擎,然后存儲引擎通過判斷索引是否符合MySQL服務器傳遞的條件,只有當索引符合條件時才會將數據檢索出來返回給MySQL服務器。

    分區分表分庫

    分區: 一張大表進行分區后,他還是一張表,不會變成二張表,但是他存放數據的區塊變多了;突破磁盤I/O瓶頸,想提高磁盤的讀寫能力
    分表: 多個表;單表的并發能力提高了,磁盤I/O性能也提高了
    分區和分表的測重點不同,分表重點是存取數據時,如何提高mysql并發能力上;而分區呢,如何突破磁盤的讀寫能力,從而達到提高mysql性能的目的。
    分庫: 單機性能不夠,分成多個庫提升性能;
    分表分庫:
    垂直切分,即將表按照功能模塊、關系密切程度劃分出來,部署到不同的庫上水平切分,當一個表中的數據量過大時,我們可以把該表的數據按照某種規則

    項目介紹

    秒殺系統

  • 用update 庫存=庫存-1 from 表 where 庫存>0;判斷是否修改成功,修改成功表示有庫存,否則就沒有; 因為update語句行級鎖,一個一個執行; 然后程序判斷修改的影響杭州是否=1;則往下走;不是則回滾; 這樣就保證了肯定不會超賣;這是樂觀鎖
    但是上面的會導致DB過載,所有的流量都去DB了不合適,所以再提出解決方案
  • 將庫存緩存起來; 用redis緩存庫存, 為了避免緩存擊穿,提前預熱緩存,為了正常的在redis減庫存,使用lua+redis來執行 判斷+-1庫存的操作 ;因為lua原子性;
  • 流量還是過大, 再加上限流; 然后描述Sentinel的簡單原理,什么漏桶算法和令牌桶算法 都描述一遍
  • 防止單用戶惡意刷腳本還有線程安全問題(單個用戶只能領取一個的判斷),加上根據用戶id維度加一個 分布式鎖
  • 如果庫存很大的情況下,在DB層面的壓力還是會很大; 熱點問題 解決方案: 分散熱點; 讓庫存量減小,分散成多個商品; 和分成多個時間段;
  • 還可以異步化, 非關鍵操作異步處理
  • 靜態資源使用CDN進行服務分發
  • 服務降級
  • 你做的這個秒殺系統QPS怎么樣

    你怎么判斷需要多少機器來承受?

    如果判斷一個機器達到了極限?CPU IO 磁盤?

    CPU
    內存使用率
    磁盤IO

    緩存穿透 緩存擊穿 緩存雪崩

  • 緩存穿透: 緩存和數據庫中都沒有數據,一直請求;導致DB壓力過大;
    解決方案: ①.數據庫中也沒有的數據,這是可以將k-v對寫成k-null,緩存的時機可以設置短一點例如20秒;可以防止請求反復攻擊同一個id;
  • 緩存擊穿:緩存擊穿是指緩存中沒有但數據庫中有的數據(一般是緩存時間到期),這時由于并發用戶特別多,同時讀緩存沒讀到數據,又同時去數據庫去取數據,引起數據庫壓力瞬間增大,造成過大壓力
    解決方案:①.像秒殺場景先緩存預熱,別讓秒殺一瞬間很多請求穿透到了DB;
    ②.設置熱點數據永不過期
  • 緩存雪崩:在同一時刻緩存中的大部分數據都過期了,導致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() 只是適用于應用程序地址空間不需要對所訪問數據進行處理的情況

    局限性:

  • sendfile() 局限于基于文件服務的網絡應用程序,比如 web 服務器。據說,在 Linux 內核中實現 sendfile() 只是為了在其他平臺上使用 sendfile() 的 Apache 程序。
  • 由于網絡傳輸具有異步性,很難在 sendfile () 系統調用的接收端進行配對的實現方式,所以數據傳輸的接收端一般沒有用到這種技術。
  • 基于性能的考慮來說,sendfile () 仍然需要有一次從文件到 socket 緩沖區的 CPU 拷貝操作,這就導致頁緩存有可能會被傳輸的數據所污染。
  • 帶有 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]之間,求是否有重復元素

    鏡像二叉樹

    二叉樹多個節點的最近公共祖先

    樹的非遞歸先序遍歷。

    總結

    以上是生活随笔為你收集整理的JAVA面试八股文的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。