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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

京东18届一年半经验社招面经

發布時間:2023/12/29 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 京东18届一年半经验社招面经 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

京東18屆一年半經驗社招面經

    • ZooKeeper
      • CAP定理
      • ZAB協議
      • Leader選舉算法和流程
    • Redis
      • Redis的應用場景
      • 單線程的Redis為什么快
      • Redis 的數據結構及使用場景(必考)
      • zset跳表的數據結構(必考)
      • Redis的數據過期策略(必考)
      • Redis的LRU過期策略的具體實現
      • 如何解決Redis緩存雪崩,緩存穿透問題
      • Redis的持久化機制(必考)
      • redis主從復制,主從同步
      • Redis和memcached的區別
      • redis并發競爭key的解決方案
      • Redis與Mysql雙寫一致性方案
      • Redis的管道pipeline
    • Mysql
      • 事務的基本要素(事務特性)
      • 事務隔離級別、如何解決事務的并發問題(臟讀,幻讀)(必考)
      • MVCC,binlog,redolog,undolog都是什么,起什么作用(必考)
      • binlog和redolog的區別
      • Mysql如何保證一致性和持久性
      • InnoDB的行鎖
      • myisam和innodb的區別,什么時候選擇myisam
      • 為什么選擇B+樹作為索引結構(必考)
      • 索引B+樹的葉子節點都可以存哪些東西(必考)
      • 查詢在什么時候不走(預期中的)索引(必考)
      • sql如何優化
      • explain是如何解析sql的
      • order by原理
    • JVM
      • 運行時數據區域(內存模型)(必考)
      • 分代回收
      • 垃圾回收機制(必考)
      • 哪些對象可以作為GC Roots
      • 垃圾回收算法(必考)
      • Minor GC和Full GC觸發條件
      • GC中Stop the world(STW)
      • 各垃圾回收器的特點及區別
      • G1和CMS的比較
      • 雙親委派模型
      • JDBC和雙親委派模型關系
    • Java并發
      • HashMap和ConcurrentHashMap區別(必考)
      • ConcurrentHashMap的數據結構(必考)
      • 高并發HashMap的環是如何產生的
      • volatile作用(必考)
      • Atomic類如何保證原子性(CAS操作)(必考)
      • CAS操作ABA問題
      • synchronized和Lock的區別(必考)
      • 線程之間如何通信
      • 為什么要使用線程池(必考)
      • 核心線程池ThreadPoolExecutor的參數(必考)
      • ThreadPoolExecutor的工作流程(必考)
      • 如何控制線程池線程的優先級
      • AQS理論的數據結構
    • Java基礎
      • HashMap如果我想要讓自己的Object作為K應該怎么辦
      • Boolean占幾個字節
      • jdk1.8/jdk1.7都分別新增了哪些特性
      • Exception和Error區別
    • Spring
      • Spring的IOC/AOP的實現(必考)
      • 動態代理的實現方式(必考)
      • Spring的后置處理器
      • Spring的@Transactional如何實現的(必考)
      • Spring的事務傳播級別
    • 消息隊列
      • 為什么需要消息隊列
      • Kafka的文件存儲機制
      • Kafka 如何保證可靠性
      • Kafka消息是采用Pull模式,還是Push模式
      • Kafka是如何實現高吞吐率的
      • Kafka判斷一個節點還活著的兩個條件
    • 操作系統
      • 進程和線程
      • 進程的組成部分
      • 進程的通信方式
      • 進程間五種通信方式的比較
      • 死鎖的4個必要條件
      • 如何避免(預防)死鎖
    • 計算機網路
      • Get和Post區別
      • Http請求的完全過程
      • tcp和udp區別
      • tcp和udp的優點
      • 三次握手
      • 為什么不能兩次握手
      • 四次揮手
      • 為什么連接的時候是三次握手,關閉的時候卻是四次握手
      • 其他
        • 高并發系統的設計與實現
        • 高并發系統的限流如何實現
      • 面試感受及評價
      • 最后

ZooKeeper

CAP定理

  • 一個分布式系統不可能同時滿足以下三種,一致性(C:Consistency),可用性(A:Available),分區容錯性(P:Partition Tolerance). 在此ZooKeeper保證的是CP,ZooKeeper不能保證每次服務請求的可用性,在極端環境下,ZooKeeper可能會丟棄一些請求,消費者程序需要重新請求才能獲得結果。 另外在進行leader選舉時集群都是不可用,所以說,ZooKeeper不能保證服務可用性。(Base理論CA強一致性和最終一致性)
  • 推薦閱讀
    • 談談分布式系統的CAP理論
    • CAP理論中的P到底是個什么意思?
    • 分布式理論(二) - BASE理論

ZAB協議

  • ZAB協議包括兩種基本的模式:崩潰恢復和消息廣播。當整個 Zookeeper 集群剛剛啟動或者Leader服務器宕機、重啟或者網絡故障導致不存在過半的服務器與 Leader 服務器保持正常通信時,所有服務器進入崩潰恢復模式,首先選舉產生新的 Leader 服務器,然后集群中 Follower 服務器開始與新的 Leader 服務器進行數據同步。 當集群中超過半數機器與該 Leader 服務器完成數據同步之后,退出恢復模式進入消息廣播模式,Leader 服務器開始接收客戶端的事務請求生成事物提案來進行事務請求處理。
  • 推薦閱讀:看大牛如何分析Zookeeper ZAB 協議

Leader選舉算法和流程

  • FastLeaderElection(默認提供的選舉算法): 目前有5臺服務器,每臺服務器均沒有數據,它們的編號分別是1,2,3,4,5,按編號依次啟動,它們的選擇舉過程如下: (1)服務器1啟動,給自己投票,然后發投票信息,由于其它機器還沒有啟動所以它收不到反饋信息,服務器1的狀態一直屬于Looking。 (2)服務器2啟動,給自己投票,同時與之前啟動的服務器1交換結果,由于服務器2的編號大所以服務器2勝出,但此時投票數沒有大于半數,所以兩個服務器的狀態依然是LOOKING。 (3)服務器3啟動,給自己投票,同時與之前啟動的服務器1,2交換信息,由于服務器3的編號最大所以服務器3勝出,此時投票數正好大于半數,所以服務器3成為leader,服務器1,2成為follower。 (4)服務器4啟動,給自己投票,同時與之前啟動的服務器1,2,3交換信息,盡管服務器4的編號大,但之前服務器3已經勝出,所以服務器4只能成為follower。 (5)服務器5啟動,后面的邏輯同服務器4成為follower。
  • 推薦閱讀:【分布式】Zookeeper的Leader選舉

Redis

Redis的應用場景

(1)緩存

(2)共享Session

(3)消息隊列系統

(4)分布式鎖

推薦閱讀:Redis常見的應用場景解析

單線程的Redis為什么快

(1)純內存操作

(2)單線程操作,避免了頻繁的上下文切換

(3)合理高效的數據結構

(4)采用了非阻塞I/O多路復用機制

Redis 的數據結構及使用場景(必考)

(1)String字符串:字符串類型是 Redis 最基礎的數據結構,首先鍵都是字符串類型,而且 其他幾種數據結構都是在字符串類型基礎上構建的,我們常使用的 set key value 命令就是字符串。常用在緩存、計數、共享Session、限速等。

(2)Hash哈希:在Redis中,哈希類型是指鍵值本身又是一個鍵值對 結構,添加命令:hset key field value。哈希可以用來存放用戶信息,比如實現購物車。

(3)List列表(雙向鏈表):列表(list)類型是用來存儲多個有序的字符串。可以做簡單的消息隊列的功能。

(4)Set集合:集合(set)類型也是用來保存多個的字符串元素,但和列表類型不一 樣的是,集合中不允許有重復元素,并且集合中的元素是無序的,不能通過 索引下標獲取元素。利用 Set 的交集、并集、差集等操作,可以計算共同喜好,全部的喜好,自己獨有的喜好等功能。

(5)Sorted Set有序集合(跳表實現):Sorted Set 多了一個權重參數 Score,集合中的元素能夠按 Score 進行排列。可以做排行榜應用,取 TOP N 操作。

zset跳表的數據結構(必考)

  • Redis 為什么用跳表而不用平衡樹?

Redis的數據過期策略(必考)

  • Redis 中數據過期策略采用定期刪除+惰性刪除策略:
    • (1)定期刪除策略:Redis 啟用一個定時器定時監視所有的 key,判斷key是否過期,過期的話就刪除。這種策略可以保證過期的 key 最終都會被刪除,但是也存在嚴重的缺點:每次都遍歷內存中所有的數據,非常消耗 CPU 資源,并且當 key 已過期,但是定時器還處于未喚起狀態,這段時間內 key 仍然可以用。
    • (2)惰性刪除策略:在獲取 key 時,先判斷 key 是否過期,如果過期則刪除。這種方式存在一個缺點:如果這個 key 一直未被使用,那么它一直在內存中,其實它已經過期了,會浪費大量的空間。 這兩種策略天然的互補,結合起來之后,定時刪除策略就發生了一些改變,不在是每次掃描全部的 key 了,而是隨機抽取一部分 key 進行檢查,這樣就降低了對 CPU 資源的損耗,惰性刪除策略互補了為檢查到的key,基本上滿足了所有要求。 但是有時候就是那么的巧,既沒有被定時器抽取到,又沒有被使用,這些數據又如何從內存中消失?沒關系,
    • 還有內存淘汰機制,當內存不夠用時,內存淘汰機制就會上場。淘汰策略分為: (1)當內存不足以容納新寫入數據時,新寫入操作會報錯。(Redis 默認策略) (2)當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的 Key。(LRU推薦使用) (3)當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個 Key。 (4)當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最近最少使用的 Key。這種情況一般是把 Redis 既當緩存,又做持久化存儲的時候才用。 (5)當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,隨機移除某個 Key。 (6)當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,有更早過期時間的 Key 優先移除。

Redis的LRU過期策略的具體實現

  • Redis的LRU具體實現: 用棧的形式會導致執行select *的時候大量非熱點數據占領頭部數據,所以需要改進。 Redis每次按key獲取一個值的時候,都會更新value中的lru字段為當前秒級別的時間戳。Redis初始的實現算法很簡單,隨機從dict中取出五個key,淘汰一個lru字段值最小的。 在3.0的時候,又改進了一版算法,首先第一次隨機選取的key都會放入一個pool中(pool的大小為16),pool中的key是按lru大小順序排列的。接下來每次隨機選取的keylru值必須小于pool中最小的lru才會繼續放入,直到將pool放滿。放滿之后,每次如果有新的key需要放入,需要將pool中lru最大的一個key取出。淘汰的時候,直接從pool中選取一個lru最小的值然后將其淘汰。
  • 推薦閱讀:Redis中的LRU淘汰策略分析

如何解決Redis緩存雪崩,緩存穿透問題

  • 緩存雪崩:
    • (1)使用 Redis 高可用架構:使用 Redis 集群來保證 Redis 服務不會掛掉
    • (2)緩存時間不一致,給緩存的失效時間,加上一個隨機值,避免集體失效
    • (3)限流降級策略:有一定的備案,比如個性推薦服務不可用了,換成熱點數據推薦服務
  • 緩存穿透
    • (1)在接口做校驗
    • (2)存null值(緩存擊穿加鎖)
    • (3)布隆過濾器攔截: 將所有可能的查詢key 先映射到布隆過濾器中,查詢時先判斷key是否存在布隆過濾器中,存在才繼續向下執行,如果不存在,則直接返回。 布隆過濾器將值進行多次哈希bit存儲,布隆過濾器說某個元素在,可能會被誤判。布隆過濾器說某個元素不在,那么一定不在。
  • 推薦閱讀:緩存穿透,緩存擊穿,緩存雪崩解決方案分析

Redis的持久化機制(必考)

  • redis為了保證效率,數據緩存在了內存中,但是會周期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件中,以保證數據的持久化。

    Redis的持久化策略有兩種:

    • (1)RDB:快照形式是直接把內存中的數據保存到一個dump的文件中,定時保存,保存策略。 當Redis需要做持久化時,Redis會fork一個子進程,子進程將數據寫到磁盤上一個臨時RDB文件中。當子進程完成寫臨時文件后,將原來的RDB替換掉。
    • (2)AOF:把所有的對Redis的服務器進行修改的命令都存到一個文件里,命令的集合。 使用AOF做持久化,每一個寫命令都通過write函數追加到appendonly.aof中。aof的默認策略是每秒鐘fsync一次,在這種配置下,就算發生故障停機,也最多丟失一秒鐘的數據。 缺點是對于相同的數據集來說,AOF的文件體積通常要大于RDB文件的體積。根據所使用的fsync策略,AOF的速度可能會慢于RDB。 Redis默認是快照RDB的持久化方式。
  • 推薦閱讀:一文看懂Redis的持久化原理

redis主從復制,主從同步

  • (1)從節點執行slaveofmasterIP,保存主節點信息

    (2)從節點中的定時任務發現主節點信息,建立和主節點的socket連接

    (3)從節點發送Ping信號,主節點返回Pong,兩邊能互相通信

    (4)連接建立后,主節點將所有數據發送給從節點(數據同步)

    (5)主節點把當前的數據同步給從節點后,便完成了復制的建立過程。接下來,主節點就會持續的把寫命令發送給從節點,保證主從數據一致性。

    主從剛剛連接的時候,進行全量同步(RDB);全同步結束后,進行增量同步(AOF)。

  • 推薦閱讀:深入學習Redis(3):主從復制

Redis和memcached的區別

  • (1)存儲方式上:memcache會把數據全部存在內存之中,斷電后會掛掉,數據不能超過內存大小。redis有部分數據存在硬盤上,這樣能保證數據的持久性。

    (2)數據支持類型上:memcache對數據類型的支持簡單,只支持簡單的key-value,而redis支持五種數據類型。

    (3)用底層模型不同:它們之間底層實現方式以及與客戶端之間通信的應用協議不一樣。redis直接自己構建了VM機制,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求。

    (4)value的大小:redis可以達到1GB,而memcache只有1MB。

redis并發競爭key的解決方案

  • (1)分布式鎖+時間戳 (2)利用消息隊列
  • 推薦閱讀:高并發架構系列:Redis并發競爭key的解決方案詳解

Redis與Mysql雙寫一致性方案

  • 先更新數據庫,再刪緩存。數據庫的讀操作的速度遠快于寫操作的,所以臟數據很難出現。可以對異步延時刪除策略,保證讀請求完成以后,再進行刪除操作。
  • 推薦閱讀:Redis與Mysql雙寫一致性方案解析

Redis的管道pipeline

  • 對于單線程阻塞式的Redis,Pipeline可以滿足批量的操作,把多個命令連續的發送給Redis Server,然后一一解析響應結果。Pipelining可以提高批量處理性能,提升的原因主要是TCP連接中減少了“交互往返”的時間。 pipeline 底層是通過把所有的操作封裝成流,redis有定義自己的出入輸出流。在 sync() 方法執行操作,每次請求放在隊列里面,解析響應包。

Mysql

事務的基本要素(事務特性)

  • (1)原子性:事務是一個原子操作單元,其對數據的修改,要么全都執行,要么全都不執行

    (2)一致性:事務開始前和結束后,數據庫的完整性約束沒有被破壞。

    (3)隔離性:同一時間,只允許一個事務請求同一數據,不同的事務之間彼此沒有任何干擾。

    (4)持久性:事務完成后,事務對數據庫的所有更新將被保存到數據庫,不能回滾。

事務隔離級別、如何解決事務的并發問題(臟讀,幻讀)(必考)

  • MySQL 四種事務隔離級的說明
  • 數據庫事務隔離級別-- 臟讀、幻讀、不可重復讀

MVCC,binlog,redolog,undolog都是什么,起什么作用(必考)

  • undolog 也就是我們常說的回滾日志文件 主要用于事務中執行失敗,進行回滾,以及MVCC中對于數據歷史版本的查看。由引擎層的InnoDB引擎實現,是邏輯日志,記錄數據修改被修改前的值,比如"把id=‘B’ 修改為id = ‘B2’ ,那么undo日志就會用來存放id ='B’的記錄”。當一條數據需要更新前,會先把修改前的記錄存儲在undolog中,如果這個修改出現異常,則會使用undo日志來實現回滾操作,保證事務的一致性。當事務提交之后,undo log并不能立馬被刪除,而是會被放到待清理鏈表中,待判斷沒有事物用到該版本的信息時才可以清理相應undolog。它保存了事務發生之前的數據的一個版本,用于回滾,同時可以提供多版本并發控制下的讀(MVCC),也即非鎖定讀。
  • redoLog 是重做日志文件是記錄數據修改之后的值,用于持久化到磁盤中。redo log包括兩部分:一是內存中的日志緩沖(redo log buffer),該部分日志是易失性的;二是磁盤上的重做日志文件(redo log file),該部分日志是持久的。由引擎層的InnoDB引擎實現,是物理日志,記錄的是物理數據頁修改的信息,比如“某個數據頁上內容發生了哪些改動”。當一條數據需要更新時,InnoDB會先將數據更新,然后記錄redoLog 在內存中,然后找個時間將redoLog的操作執行到磁盤上的文件上。不管是否提交成功我都記錄,你要是回滾了,那我連回滾的修改也記錄。它確保了事務的持久性。
  • MVCC多版本并發控制是MySQL中基于樂觀鎖理論實現隔離級別的方式,用于讀已提交和可重復讀取隔離級別的實現。在MySQL中,會在表中每一條數據后面添加兩個字段:最近修改該行數據的事務ID,指向該行(undolog表中)回滾段的指針。Read View判斷行的可見性,創建一個新事務時,copy一份當前系統中的活躍事務列表。意思是,當前不應該被本事務看到的其他事務id列表。
  • binlog由Mysql的Server層實現,是邏輯日志,記錄的是sql語句的原始邏輯,比如"把id=‘B’ 修改為id = ‘B2’。binlog會寫入指定大小的物理文件中,是追加寫入的,當前文件寫滿則會創建新的文件寫入。 產生:事務提交的時候,一次性將事務中的sql語句,按照一定的格式記錄到binlog中。用于復制和恢復在主從復制中,從庫利用主庫上的binlog進行重播(執行日志中記錄的修改邏輯),實現主從同步。業務數據不一致或者錯了,用binlog恢復。
  • 推薦閱讀MySQL(5)| 五分鐘搞清楚 MVCC 機制

binlog和redolog的區別

  • 區別:
    • redolog是在InnoDB存儲引擎層產生,而binlog是MySQL數據庫的上層服務層產生的。
    • 兩種日志記錄的內容形式不同。MySQL的binlog是邏輯日志,其記錄是對應的SQL語句。而innodb存儲引擎層面的重做日志是物理日志。
    • 兩種日志與記錄寫入磁盤的時間點不同,binlog日志只在事務提交完成后進行一次寫入。而innodb存儲引擎的重做日志在事務進行中不斷地被寫入,并日志不是隨事務提交的順序進行寫入的。
    • binlog不是循環使用,在寫滿或者重啟之后,會生成新的binlog文件,redolog是循環使用。
    • binlog可以作為恢復數據使用,主從復制搭建,redolog作為異常宕機或者介質故障后的數據恢復使用。
  • 推薦閱讀:一文帶你看懂binlog和redo log

Mysql如何保證一致性和持久性

  • MySQL為了保證ACID中的一致性和持久性,使用了WAL(Write-Ahead Logging,先寫日志再寫磁盤)。Redo log就是一種WAL的應用。當數據庫忽然掉電,再重新啟動時,MySQL可以通過Redo log還原數據。也就是說,每次事務提交時,不用同步刷新磁盤數據文件,只需要同步刷新Redo log就足夠了。

InnoDB的行鎖

  • 共享鎖(S):用法lock in share mode,又稱讀鎖,允許一個事務去讀一行,阻止其他事務獲得相同數據集的排他鎖。若事務T對數據對象A加上S鎖,則事務T可以讀A但不能修改A,其他事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這保證了其他事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。
  • 排他鎖(X):用法for update,又稱寫鎖,允許獲取排他鎖的事務更新數據,阻止其他事務取得相同的數據集共享讀鎖和排他寫鎖。若事務T對數據對象A加上X鎖,事務T可以讀A也可以修改A,其他事務不能再對A加任何鎖,直到T釋放A上的鎖。在沒有索引的情況下,InnoDB只能使用表鎖。
  • 推薦閱讀:mysql鎖——innodb的行級鎖

myisam和innodb的區別,什么時候選擇myisam

  • https://blog.csdn.net/u010598360/article/details/81482225

為什么選擇B+樹作為索引結構(必考)

  • Hash索引:Hash索引底層是哈希表,哈希表是一種以key-value存儲數據的結構,所以多個數據在存儲關系上是完全沒有任何順序關系的,所以,對于區間查詢是無法直接通過索引查詢的,就需要全表掃描。所以,哈希索引只適用于等值查詢的場景。而B+ 樹是一種多路平衡查詢樹,所以他的節點是天然有序的(左子節點小于父節點、父節點小于右子節點),所以對于范圍查詢的時候不需要做全表掃描
  • 二叉查找樹:解決了排序的基本問題,但是由于無法保證平衡,可能退化為鏈表。
  • 平衡二叉樹:通過旋轉解決了平衡的問題,但是旋轉操作效率太低。
  • 紅黑樹:通過舍棄嚴格的平衡和引入紅黑節點,解決了 AVL旋轉效率過低的問題,但是在磁盤等場景下,樹仍然太高,IO次數太多。
  • B+樹:在B樹的基礎上,將非葉節點改造為不存儲數據純索引節點,進一步降低了樹的高度;此外將葉節點使用指針連接成鏈表,范圍查詢更加高效。
  • 推薦閱讀:一步步分析為什么B+樹適合作為索引的結構 以及索引原理 (阿里面試)

索引B+樹的葉子節點都可以存哪些東西(必考)

  • 可能存儲的是整行數據,也有可能是主鍵的值。B+樹的葉子節點存儲了整行數據的是主鍵索引,也被稱之為聚簇索引。而索引B+ Tree的葉子節點存儲了主鍵的值的是非主鍵索引,也被稱之為非聚簇索引
  • 推薦閱讀:什么是覆蓋索引?如何利用覆蓋索引進行SQL語句優化?

查詢在什么時候不走(預期中的)索引(必考)

  • 模糊查詢 %like
  • 索引列參與計算,使用了函數
  • 非最左前綴順序
  • where對null判斷
  • where不等于
  • or操作有至少一個字段沒有索引
  • 需要回表的查詢結果集過大(超過配置的范圍)
    • 推薦閱讀:

      MySQL高級 之 索引失效與優化詳解

      sql優化的幾種方式

    sql如何優化

  • 創建并使用正確的索引
  • 只返回需要的字段
  • 減少交互次數(批量提交)
  • 設置合理的Fetch Size(數據每次返回給客戶端的條數)
  • explain是如何解析sql的

    • 推薦閱讀:MySQL 性能優化神器 Explain 使用分析

    order by原理

    • order by的工作原理

    JVM

    運行時數據區域(內存模型)(必考)

  • 程序計數器:程序計數器是一塊較小的內存空間,它可以看作是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型里,字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。是線程私有”的內存。
  • Java虛擬機棧:與程序計數器一樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀 ,用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。
  • 本地方法棧:本地方法棧(Native Method Stack)與虛擬機棧所發揮的作用是非常相似的,它們之間的區別不過是虛擬機棧為虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則為虛擬機使用到的Native方法服務。
  • Java堆:對于大多數應用來說,Java堆是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內存。
  • 方法區(1.8叫元數據):方法區與 Java 堆一樣,是各個線程共享的內存區域,它用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。
  • 分代回收

    • HotSpot JVM把年輕代分為了三部分:1個Eden區和2個Survivor區(分別叫from和to)。一般情況下,新創建的對象都會被分配到Eden區(一些大對象特殊處理),這些對象經過第一次Minor GC后,如果仍然存活,將會被移到Survivor區。對象在Survivor區中每熬過一次Minor GC,年齡就會增加1歲,當它的年齡增加到一定程度時,就會被移動到年老代中。

    • 因為年輕代中的對象基本都是朝生夕死的,所以在年輕代的垃圾回收算法使用的是復制算法,復制算法的基本思想就是將內存分為兩塊,每次只用其中一塊,當這一塊內存用完,就將還活著的對象復制到另外一塊上面。復制算法不會產生內存碎片。

    • 在GC開始的時候,對象只會存在于Eden區和名為“From”的Survivor區,Survivor區“To”是空的。緊接著進行GC,Eden區中所有存活的對象都會被復制到“To”,而在“From”區中,仍存活的對象會根據他們的年齡值來決定去向。年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設置)的對象會被移動到年老代中,沒有達到閾值的對象會被復制到“To”區域。經過這次GC后,Eden區和From區已經被清空。這個時候,“From”和“To”會交換他們的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎樣,都會保證名為To的Survivor區域是空的。Minor GC會一直重復這樣的過程,直到“To”區被填滿,“To”區被填滿之后,會將所有對象移動到年老代中。

    垃圾回收機制(必考)

    • 引用計數法:引用計數法是一種簡單但速度很慢的垃圾回收技術。每個對象都含有一個引用計數器,當有引用連接至對象時,引用計數加1。當引用離開作用域或被置為null時,引用計數減1。雖然管理引用計數的開銷不大,但這項開銷在整個程序生命周期中將持續發生。垃圾回收器會在含有全部對象的列表上遍歷,當發現某個對象引用計數為0時,就釋放其占用的空間。
    • 可達性分析算法:這個算法的基本思路就是通過一系列的稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots到這個對象不可達)時,則證明此對象是不可用的。

    哪些對象可以作為GC Roots

    • 虛擬機棧(棧幀中的本地變量表)中引用的對象。
    • 方法區中類靜態屬性引用的對象。
    • 方法區中常量引用的對象。
    • 本地方法棧中JNI(即一般說的Native方法)引用的對象。

    垃圾回收算法(必考)

    • 復制:先暫停程序的運行,然后將所有存活的對象從當前堆復制到另一個堆,沒有被復制的對象全部都是垃圾。當對象被復制到新堆時,它們是一個挨著一個的,所以新堆保持緊湊排列,然后就可以按前述方法簡單,直接的分配了。缺點是一浪費空間,兩個堆之間要來回倒騰,二是當程序進入穩定態時,可能只會產生極少的垃圾,甚至不產生垃圾,盡管如此,復制式回收器仍會將所有內存自一處復制到另一處。
    • 標記-清除:同樣是從堆棧和靜態存儲區出發,遍歷所有的引用,進而找出所有存活的對象。每當它找到一個存活的對象,就會給對象一個標記,這個過程中不會回收任何對象。只有全部標記工作完成的時候,清理動作才會開始。在清理過程中,沒有標記的對象會被釋放,不會發生任何復制動作。所以剩下的堆空間是不連續的,垃圾回收器如果要希望得到連續空間的話,就得重新整理剩下的對象。
    • 標記-整理:它的第一個階段與標記/清除算法是一模一樣的,均是遍歷GC Roots,然后將存活的對象標記。移動所有存活的對象,且按照內存地址次序依次排列,然后將末端內存地址以后的內存全部回收。因此,第二階段才稱為整理階段。
    • 分代收集算法:把Java堆分為新生代和老年代,然后根據各個年代的特點采用最合適的收集算法。新生代中,對象的存活率比較低,所以選用復制算法,老年代中對象存活率高且沒有額外空間對它進行分配擔保,所以使用“標記-清除”或“標記-整理”算法進行回收。

    Minor GC和Full GC觸發條件

    • Minor GC觸發條件:當Eden區滿時,觸發Minor GC。
    • Full GC觸發條件:
    • 調用System.gc時,系統建議執行Full GC,但是不必然執行
    • 老年代空間不足
    • 方法區空間不足
    • 通過Minor GC后進入老年代的平均大小大于老年代的可用內存
    • 由Eden區、From Space區向To Space區復制時,對象大小大于To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小于該對象大小

    GC中Stop the world(STW)

    • 在執行垃圾收集算法時,Java應用程序的其他所有除了垃圾收集收集器線程之外的線程都被掛起。此時,系統只能允許GC線程進行運行,其他線程則會全部暫停,等待GC線程執行完畢后才能再次運行。這些工作都是由虛擬機在后臺自動發起和自動完成的,是在用戶不可見的情況下把用戶正常工作的線程全部停下來,這對于很多的應用程序,尤其是那些對于實時性要求很高的程序來說是難以接受的。

      但不是說GC必須STW,你也可以選擇降低運行速度但是可以并發執行的收集算法,這取決于你的業務。

    各垃圾回收器的特點及區別

    • 新生代收集器

      • Serial收集器
      • ParNew 收集器
      • Parallel Scavenge 收集器
    • 老年代收集器

      • Serial Old收集器
      • Parallel Old收集器
      • CMS收集器
      • G1收集器
    • 推薦閱讀:

      JVM垃圾回收

      深入理解JVM(3)——7種垃圾收集器

    G1和CMS的比較

  • CMS收集器是獲取最短回收停頓時間為目標的收集器,因為CMS工作時,GC工作線程與用戶線程可以并發執行,以此來達到降低手機停頓時間的目的(只有初始標記和重新標記會STW)。但是CMS收集器對CPU資源非常敏感。在并發階段,雖然不會導致用戶線程停頓,但是會占用CPU資源而導致引用程序變慢,總吞吐量下降。
  • CMS僅作用于老年代,是基于標記清除算法,所以清理的過程中會有大量的空間碎片。
  • CMS收集器無法處理浮動垃圾,由于CMS并發清理階段用戶線程還在運行,伴隨程序的運行自熱會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之后,CMS無法在本次收集中處理它們,只好留待下一次GC時將其清理掉。
  • G1是一款面向服務端應用的垃圾收集器,適用于多核處理器、大內存容量的服務端系統。G1能充分利用CPU、多核環境下的硬件優勢,使用多個CPU(CPU或者CPU核心)來縮短STW的停頓時間,它滿足短時間停頓的同時達到一個高的吞吐量。
  • 從JDK 9開始,G1成為默認的垃圾回收器。當應用有以下任何一種特性時非常適合用G1:Full GC持續時間太長或者太頻繁;對象的創建速率和存活率變動很大;應用不希望停頓時間長(長于0.5s甚至1s)。
  • G1將空間劃分成很多塊(Region),然后他們各自進行回收。堆比較大的時候可以采用,采用復制算法,碎片化問題不嚴重。整體上看屬于標記整理算法,局部(region之間)屬于復制算法。
  • G1 需要記憶集 (具體來說是卡表)來記錄新生代和老年代之間的引用關系,這種數據結構在 G1 中需要占用大量的內存,可能達到整個堆內存容量的 20% 甚至更多。而且 G1 中維護記憶集的成本較高,帶來了更高的執行負載,影響效率。所以 CMS 在小內存應用上的表現要優于 G1,而大內存應用上 G1 更有優勢,大小內存的界限是6GB到8GB。
  • 雙親委派模型

    • 雙親委派的意思是如果一個類加載器需要加載類,那么首先它會把這個類請求委派給父類加載器去完成,每一層都是如此。一直遞歸到頂層,當父加載器無法完成這個請求時,子類才會嘗試去加載。

    • 推薦閱讀:

      淺談雙親委派和破壞雙親委派

      雙親委派模型與自定義類加載器

    JDBC和雙親委派模型關系

    • 因為類加載器受到加載范圍的限制,在某些情況下父類加載器無法加載到需要的文件,這時候就需要委托子類加載器去加載class文件。

    • 推薦閱讀:

      阿里面試題:JDBC、Tomcat為什么要破壞雙親委派模型

      面試官:說說雙親委派模型?

    Java并發

    HashMap和ConcurrentHashMap區別(必考)

    • 由于HashMap是線程不同步的,雖然處理數據的效率高,但是在多線程的情況下存在著安全問題,因此設計了CurrentHashMap來解決多線程安全問題。

    • HashMap在put的時候,插入的元素超過了容量(由負載因子決定)的范圍就會觸發擴容操作,就是rehash,這個會重新將原數組的內容重新hash到新的擴容數組中,在多線程的環境下,存在同時其他的元素也在進行put操作,如果hash值相同,可能出現同時在同一數組下用鏈表表示,造成閉環,導致在get時會出現死循環,所以HashMap是線程不安全的。

    ConcurrentHashMap的數據結構(必考)

    • 在JDK1.7版本中,ConcurrentHashMap維護了一個Segment數組,Segment這個類繼承了重入鎖ReentrantLock,并且該類里面維護了一個 HashEntry<K,V>[] table數組,在寫操作put,remove,擴容的時候,會對Segment加鎖,所以僅僅影響這個Segment,不同的Segment還是可以并發的,所以解決了線程的安全問題,同時又采用了分段鎖也提升了并發的效率。在JDK1.8版本中,ConcurrentHashMap摒棄了Segment的概念,而是直接用Node數組+鏈表+紅黑樹的數據結構來實現,并發控制使用Synchronized和CAS來操作,整個看起來就像是優化過且線程安全的HashMap。

    • 推薦閱讀:HashMap? ConcurrentHashMap? 相信看完這篇沒人能難住你!

    高并發HashMap的環是如何產生的

    • HashMap的環:若當前線程此時獲得ertry節點,但是被線程中斷無法繼續執行,此時線程二進入transfer函數,并把函數順利執行,此時新表中的某個位置有了節點,之后線程一獲得執行權繼續執行,因為并發transfer,所以兩者都是擴容的同一個鏈表,當線程一執行到e.next = new table[i] 的時候,由于線程二之前數據遷移的原因導致此時new table[i] 上就有ertry存在,所以線程一執行的時候,會將next節點,設置為自己,導致自己互相使用next引用對方,因此產生鏈表,導致死循環。

    • 推薦閱讀:老生常談,HashMap的死循環

    volatile作用(必考)

    • volatile在多處理器開發中保證了共享變量的“ 可見性”。可見性的意思是當一個線程修改一個共享變量時,另外一個線程能讀到這個修改的值。(共享內存,私有內存)

    • volatile關鍵字通過“內存屏障”來防止指令被重排序。

    • 推薦閱讀:Java并發編程:volatile關鍵字解析

    Atomic類如何保證原子性(CAS操作)(必考)

    • CAS是英文單詞CompareAndSwap的縮寫,中文意思是:比較并替換。CAS需要有3個操作數:內存地址V,舊的預期值A,即將要更新的目標值B。CAS指令執行時,當且僅當內存地址V的值與預期值A相等時,將內存地址V的值修改為B,否則就什么都不做。整個比較并替換的操作是一個原子操作。如 Intel 處理器,比較并交換通過指令的 cmpxchg 系列實現。

    CAS操作ABA問題

    • 如果在這段期間它的值曾經被改成了B,后來又被改回為A,那CAS操作就會誤認為它從來沒有被改變過。Java并發包為了解決這個問題,提供了一個帶有標記的原子引用類“AtomicStampedReference”,它可以通過控制變量值的版本來保證CAS的正確性。
    • 推薦閱讀:Java CAS 原理剖析

    synchronized和Lock的區別(必考)

  • 首先synchronized是java內置關鍵字在jvm層面,Lock是個java類。
  • synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖,并且可以主動嘗試去獲取鎖。
  • synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖。
  • 用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了。
  • synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可)
  • Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。
    • 推薦閱讀:談談-synchronized和reentrantlock-的區別

    線程之間如何通信

    • 利用最基本的synchronized

    • 利用synchronized、notify、wait

    • while輪詢的方式

    • 利用Lock和Condition

    • 利用volatile

    • 利用AtomicInteger

    • 利用CyclicBarrier

    • 利用PipedInputStream

    • 利用BlockingQueue

    • 推薦閱讀:JAVA線程間通信的幾種方式

    為什么要使用線程池(必考)

    • 降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。
    • 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
    • 提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。

    核心線程池ThreadPoolExecutor的參數(必考)

    • corePoolSize:指定了線程池中的線程數量
    • maximumPoolSize:指定了線程池中的最大線程數量
    • keepAliveTime:線程池維護線程所允許的空閑時間
    • unit: keepAliveTime 的單位。
    • workQueue:任務隊列,被提交但尚未被執行的任務。
    • threadFactory:線程工廠,用于創建線程,一般用默認的即可。
    • handler:拒絕策略。當任務太多來不及處理,如何拒絕任務。
    • 推薦閱讀:threadpoolexecutor構造函數重要參數分析

    ThreadPoolExecutor的工作流程(必考)

    線程池的線程執行規則跟任務隊列有很大的關系。

    • 下面都假設任務隊列沒有大小限制:
      • 如果線程數量<=核心線程數量,那么直接啟動一個核心線程來執行任務,不會放入隊列中。
      • 如果線程數量>核心線程數,但<=最大線程數,并且任務隊列是LinkedBlockingDeque的時候,超過核心線程數量的任務會放在任務隊列中排隊。
      • 如果線程數量>核心線程數,但<=最大線程數,并且任務隊列是SynchronousQueue的時候,線程池會創建新線程執行任務,這些任務也不會被放在任務隊列中。這些線程屬于非核心線程,在任務完成后,閑置時間達到了超時時間就會被清除。
      • 如果線程數量>核心線程數,并且>最大線程數,當任務隊列是LinkedBlockingDeque,會將超過核心線程的任務放在任務隊列中排隊。也就是當任務隊列是LinkedBlockingDeque并且沒有大小限制時,線程池的最大線程數設置是無效的,他的線程數最多不會超過核心線程數。
      • 如果線程數量>核心線程數,并且>最大線程數,當任務隊列是SynchronousQueue的時候,會因為線程池拒絕添加任務而拋出異常。
    • 任務隊列大小有限時
      • 當LinkedBlockingDeque塞滿時,新增的任務會直接創建新線程來執行,當創建的線程數量超過最大線程數量時會拋異常。
      • SynchronousQueue沒有數量限制。因為他根本不保持這些任務,而是直接交給線程池去執行。當任務數量超過最大線程數時會直接拋異常。
    • 推薦閱讀:Java多線程-線程池ThreadPoolExecutor構造方法和規則

    如何控制線程池線程的優先級

    • 把現有線程池改為優先級隊列
    • 推薦閱讀:基于優先級隊列java線程池

    AQS理論的數據結構

    • AQS內部有3個對象,一個是state(用于計數器,類似gc的回收計數器),一個是線程標記(當前線程是誰加鎖的),一個是阻塞隊列。

    • 推薦閱讀:AQS原理及其同步組件總結

    Java基礎

    HashMap如果我想要讓自己的Object作為K應該怎么辦

  • 重寫hashCode()是因為需要計算存儲數據的存儲位置,需要注意不要試圖從散列碼計算中排除掉一個對象的關鍵部分來提高性能,這樣雖然能更快但可能會導致更多的Hash碰撞;
  • 重寫equals()方法,需要遵守自反性、對稱性、傳遞性、一致性以及對于任何非null的引用值x,x.equals(null)必須返回false的這幾個特性,目的是為了保證key在哈希表中的唯一性;
  • Boolean占幾個字節

    未精確定義字節。Java語言表達式所操作的boolean值,在編譯之后都使用Java虛擬機中的int數據類型來代替,而boolean數組將會被編碼成Java虛擬機的byte數組,每個元素boolean元素占8位。

    jdk1.8/jdk1.7都分別新增了哪些特性

    • [Jdk1.7與 jdk1.8的區別和最新的特征](https://swenfang.github.io/2019/05/12/面試總結/Jdk1.7與 jdk1.8的區別和最新的特征/)

    Exception和Error區別

    • Error(錯誤):是程序無法處理的錯誤,表示運行應用程序中較嚴重問題。大多數錯誤與代碼編寫者執行的操作無關,而表示代碼運行時 JVM(Java 虛擬機)出現的問題。例如,Java虛擬機運行錯誤(Virtual MachineError),當 JVM 不再有繼續執行操作所需的內存資源時,將出現 OutOfMemoryError。這些異常發生時,Java虛擬機(JVM)一般會選擇線程終止。這些錯誤表示故障發生于虛擬機自身、或者發生在虛擬機試圖執行應用時,如Java虛擬機運行錯誤(Virtual MachineError)、類定義錯誤(NoClassDefFoundError)等。這些錯誤是不可查的,因為它們在應用程序的控制和處理能力之 外,而且絕大多數是程序運行時不允許出現的狀況。對于設計合理的應用程序來說,即使確實發生了錯誤,本質上也不應該試圖去處理它所引起的異常狀況。在 Java中,錯誤通過Error的子類描述。

    • Exception(異常):是程序本身可以處理的異常。Exception 類有一個重要的子類 RuntimeException。RuntimeException 異常由Java虛擬機拋出。NullPointerException(要訪問的變量沒有引用任何對象時,拋出該異常)、ArithmeticException(算術運算異常,一個整數除以0時,拋出該異常)和 ArrayIndexOutOfBoundsException (下標越界異常)。

    Spring

    Spring的IOC/AOP的實現(必考)

    • IOC(控制反轉)就是依賴倒置原則的一種代碼設計思路。就是把原先在代碼里面需要實現的對象創建、對象之間的依賴,反轉給容器來幫忙實現。
    • AOP底層實現原理:動態代理
    • 推薦閱讀:理解Spring的AOP和IOC實現原理

    動態代理的實現方式(必考)

    • JDK動態代理:利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。

    • CGlib動態代理:利用ASM(開源的Java字節碼編輯庫,操作字節碼)開源包,將代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。

    • 區別:JDK代理只能對實現接口的類生成代理;CGlib是針對類實現代理,對指定的類生成一個子類,并覆蓋其中的方法,這種通過繼承類的實現方式,不能代理final修飾的類。

    • Spring AOP 基于AspectJ注解如何實現AOP : AspectJ是一個AOP框架,它能夠對java代碼進行AOP編譯(一般在編譯期進行),讓java代碼具有AspectJ的AOP功能(當然需要特殊的編譯器)

    Spring的后置處理器

    • BeanPostProcessor:Bean的后置處理器,主要在bean初始化前后工作。

    • InstantiationAwareBeanPostProcessor:繼承于BeanPostProcessor,主要在實例化bean前后工作; AOP創建代理對象就是通過該接口實現。

    • BeanFactoryPostProcessor:Bean工廠的后置處理器,在bean定義(bean definitions)加載完成后,bean尚未初始化前執行。

    • BeanDefinitionRegistryPostProcessor:繼承于BeanFactoryPostProcessor。其自定義的方法postProcessBeanDefinitionRegistry會在bean定義(bean definitions)將要加載,bean尚未初始化前真執行,即在BeanFactoryPostProcessor的postProcessBeanFactory方法前被調用。

    • 推薦閱讀:bean的生命周期

    Spring的@Transactional如何實現的(必考)

    • 配置文件開啟注解驅動,在相關的類和方法上通過注解@Transactional標識。

    • spring 在啟動的時候會去解析生成相關的bean,這時候會查看擁有相關注解的類和方法,并且為這些類和方法生成代理,并根據@Transaction的相關參數進行相關配置注入,這樣就在代理中為我們把相關的事務處理掉了(開啟正常提交事務,異常回滾事務)。

    • 真正的數據庫層的事務提交和回滾是通過binlog或者redo log實現的。

    • 推薦閱讀:@Transaction必知必會

    Spring的事務傳播級別

    • REQUIRED(默認):支持使用當前事務,如果當前事務不存在,創建一個新事務。

    • SUPPORTS:支持使用當前事務,如果當前事務不存在,則不使用事務。

    • MANDATORY:強制,支持使用當前事務,如果當前事務不存在,則拋出Exception。

    • REQUIRES_NEW:創建一個新事務,如果當前事務存在,把當前事務掛起。

    • NOT_SUPPORTED:無事務執行,如果當前事務存在,把當前事務掛起。

    • NEVER:無事務執行,如果當前有事務則拋出Exception。

    • NESTED:嵌套事務,如果當前事務存在,那么在嵌套的事務中執行。如果當前事務不存在,則表現跟REQUIRED一樣。

    • 推薦閱讀:Spring事務傳播機制

    消息隊列

    為什么需要消息隊列

    • 異步:主系統接收一個請求,在本地執行完SQL以后,需要分別調用A,B,C三個子系統的接口,執行時間分別為200ms,100ms,300ms,則總的執行時間為10+200+100+300 = 610(ms)。但是一旦使用了MQ之后,主系統只需要發送3條消息到MQ中的3個消息隊列,然后就返回給用戶了。消息發送到MQ耗時20ms,那么用戶感知到這個接口的總時間就為10+20=30(ms)。

    • 解耦:開始的時候,主系統在用戶發生某個操作的時候,需要把用戶提交的數據同時推送到A、B兩個系統的時候。
      隨著業務快速迭代,這個時候系統C,D也想要這個數據,主系統修改接口,增加C,D的接入
      隨著業務再迭代,這個時候系統B不要這個數據,主系統修改接口,刪除B的接入
      … …業務不斷迭代, 主系統需要不斷調整接口。

      引入MQ以后,主系統只負責將生產的數據投遞到MQ,其它事情不用關心。各個子系統可以隨時訂閱/取消對消息的消費。

    • 削峰填谷:DB支持的最大QPS為1000,平常的時候,用戶訪問請求為100QPS,系統訪問正常。 高峰的時候,大量用戶請求瞬間涌入,DB的請求達到5000QPS,直接被打死,絕望。

      引入MQ以后,消息被MQ保存起來了,然后系統就可以按照自己的消費能力來消費,比如每秒1000個數據,這樣慢慢寫入數據庫,這樣就不會打死數據庫了:

    Kafka的文件存儲機制

    • Kafka中消息是以topic進行分類的,生產者通過topic向Kafka broker發送消息,消費者通過topic讀取數據。然而topic在物理層面又能以partition為分組,一個topic可以分成若干個partition。partition還可以細分為segment,一個partition物理上由多個segment組成,segment文件由兩部分組成,分別為“.index”文件和“.log”文件,分別表示為segment索引文件和數據文件。這兩個文件的命令規則為:partition全局的第一個segment從0開始,后續每個segment文件名為上一個segment文件最后一條消息的offset值。

    Kafka 如何保證可靠性

    如果我們要往 Kafka 對應的主題發送消息,我們需要通過 Producer 完成。前面我們講過 Kafka 主題對應了多個分區,每個分區下面又對應了多個副本;為了讓用戶設置數據可靠性, Kafka 在 Producer 里面提供了消息確認機制。也就是說我們可以通過配置來決定消息發送到對應分區的幾個副本才算消息發送成功。可以在定義 Producer 時通過 acks 參數指定。這個參數支持以下三種值:

    • acks = 0:意味著如果生產者能夠通過網絡把消息發送出去,那么就認為消息已成功寫入 Kafka 。在這種情況下還是有可能發生錯誤,比如發送的對象無能被序列化或者網卡發生故障,但如果是分區離線或整個集群長時間不可用,那就不會收到任何錯誤。在 acks=0 模式下的運行速度是非常快的(這就是為什么很多基準測試都是基于這個模式),你可以得到驚人的吞吐量和帶寬利用率,不過如果選擇了這種模式, 一定會丟失一些消息。
    • acks = 1:意味若 Leader 在收到消息并把它寫入到分區數據文件(不一定同步到磁盤上)時會返回確認或錯誤響應。在這個模式下,如果發生正常的 Leader 選舉,生產者會在選舉時收到一個 LeaderNotAvailableException 異常,如果生產者能恰當地處理這個錯誤,它會重試發送悄息,最終消息會安全到達新的 Leader 那里。不過在這個模式下仍然有可能丟失數據,比如消息已經成功寫入 Leader,但在消息被復制到 follower 副本之前 Leader發生崩潰。
    • acks = all(這個和 request.required.acks = -1 含義一樣):意味著 Leader 在返回確認或錯誤響應之前,會等待所有同步副本都收到悄息。如果和min.insync.replicas 參數結合起來,就可以決定在返回確認前至少有多少個副本能夠收到悄息,生產者會一直重試直到消息被成功提交。不過這也是最慢的做法,因為生產者在繼續發送其他消息之前需要等待所有副本都收到當前的消息。

    Kafka消息是采用Pull模式,還是Push模式

    Kafka最初考慮的問題是,customer應該從brokes拉取消息還是brokers將消息推送到consumer,也就是pull還push。在這方面,Kafka遵循了一種大部分消息系統共同的傳統的設計:producer將消息推送到broker,consumer從broker拉取消息。push模式下,當broker推送的速率遠大于consumer消費的速率時,consumer恐怕就要崩潰了。最終Kafka還是選取了傳統的pull模式。Pull模式的另外一個好處是consumer可以自主決定是否批量的從broker拉取數據。Pull有個缺點是,如果broker沒有可供消費的消息,將導致consumer不斷在循環中輪詢,直到新消息到達。為了避免這點,Kafka有個參數可以讓consumer阻塞知道新消息到達。

    Kafka是如何實現高吞吐率的

  • 順序讀寫:kafka的消息是不斷追加到文件中的,這個特性使kafka可以充分利用磁盤的順序讀寫性能
  • 零拷貝:跳過“用戶緩沖區”的拷貝,建立一個磁盤空間和內存的直接映射,數據不再復制到“用戶態緩沖區”
  • 文件分段:kafka的隊列topic被分為了多個區partition,每個partition又分為多個段segment,所以一個隊列中的消息實際上是保存在N多個片段文件中
  • 批量發送:Kafka允許進行批量發送消息,先將消息緩存在內存中,然后一次請求批量發送出去
  • 數據壓縮:Kafka還支持對消息集合進行壓縮,Producer可以通過GZIP或Snappy格式對消息集合進行壓縮
  • Kafka判斷一個節點還活著的兩個條件

  • 節點必須可以維護和 ZooKeeper 的連接,Zookeeper 通過心跳機制檢查每個節點的連接
  • 如果節點是個 follower,他必須能及時的同步 leader 的寫操作,延時不能太久
  • 操作系統

    進程和線程

  • **進程是操作系統資源分配的最小單位,線程是CPU任務調度的最小單位。**一個進程可以包含多個線程,所以進程和線程都是一個時間段的描述,是CPU工作時間段的描述,不過是顆粒大小不同。
  • 不同進程間數據很難共享,同一進程下不同線程間數據很易共享。
  • 每個進程都有獨立的代碼和數據空間,進程要比線程消耗更多的計算機資源。線程可以看做輕量級的進程,同一類線程共享代碼和數據空間,每個線程都有自己獨立的運行棧和程序計數器,線程之間切換的開銷小。
  • 進程間不會相互影響,一個線程掛掉將導致整個進程掛掉。
  • 系統在運行的時候會為每個進程分配不同的內存空間;而對線程而言,除了CPU外,系統不會為線程分配內存(線程所使用的資源來自其所屬進程的資源),線程組之間只能共享資源。
  • 進程的組成部分

    進程由進程控制塊(PCB)、程序段、數據段三部分組成。

    進程的通信方式

    • 無名管道:半雙工的,即數據只能在一個方向上流動,只能用于具有親緣關系的進程之間的通信,可以看成是一種特殊的文件,對于它的讀寫也可以使用普通的read、write 等函數。但是它不是普通的文件,并不屬于其他任何文件系統,并且只存在于內存中。
    • FIFO命名管道:FIFO是一種文件類型,可以在無關的進程之間交換數據,與無名管道不同,FIFO有路徑名與之相關聯,它以一種特殊設備文件形式存在于文件系統中。
    • 消息隊列:消息隊列,是消息的鏈接表,存放在內核中。一個消息隊列由一個標識符(即隊列ID)來標識。
    • 信號量:信號量是一個計數器,信號量用于實現進程間的互斥與同步,而不是用于存儲進程間通信數據。
    • 共享內存:共享內存指兩個或多個進程共享一個給定的存儲區,一般配合信號量使用。

    進程間五種通信方式的比較

  • 管道:速度慢,容量有限,只有父子進程能通訊。
  • FIFO:任何進程間都能通訊,但速度慢。
  • 消息隊列:容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完數據的問題。
  • 信號量:不能傳遞復雜消息,只能用來同步。
  • 共享內存區:能夠很容易控制容量,速度快,但要保持同步,比如一個進程在寫的時候,另一個進程要注意讀寫的問題,相當于線程中的線程安全,當然,共享內存區同樣可以用作線程間通訊,不過沒這個必要,線程間本來就已經共享了同一進程內的一塊內存。
  • 死鎖的4個必要條件

  • 互斥條件:一個資源每次只能被一個線程使用;
  • 請求與保持條件:一個線程因請求資源而阻塞時,對已獲得的資源保持不放;
  • 不剝奪條件:進程已經獲得的資源,在未使用完之前,不能強行剝奪;
  • 循環等待條件:若干線程之間形成一種頭尾相接的循環等待資源關系。
  • 如何避免(預防)死鎖

  • 破壞“請求和保持”條件:讓進程在申請資源時,一次性申請所有需要用到的資源,不要一次一次來申請,當申請的資源有一些沒空,那就讓線程等待。不過這個方法比較浪費資源,進程可能經常處于饑餓狀態。還有一種方法是,要求進程在申請資源前,要釋放自己擁有的資源。
  • 破壞“不可搶占”條件:允許進程進行搶占,方法一:如果去搶資源,被拒絕,就釋放自己的資源。方法二:操作系統允許搶,只要你優先級大,可以搶到。
  • 破壞“循環等待”條件:將系統中的所有資源統一編號,進程可在任何時刻提出資源申請,但所有申請必須按照資源的編號順序提出(指定獲取鎖的順序,順序加鎖)。
  • 計算機網路

    Get和Post區別

  • Get是不安全的,因為在傳輸過程,數據被放在請求的URL中;Post的所有操作對用戶來說都是不可見的。
  • Get傳送的數據量較小,這主要是因為受URL長度限制;Post傳送的數據量較大,一般被默認為不受限制。
  • Get限制Form表單的數據集的值必須為ASCII字符;而Post支持整個ISO10646字符集。
  • Get執行效率卻比Post方法好。Get是form提交的默認方法。
  • GET產生一個TCP數據包;POST產生兩個TCP數據包。(非必然,客戶端可靈活決定)
  • Http請求的完全過程

  • 瀏覽器根據域名解析IP地址(DNS),并查DNS緩存
  • 瀏覽器與WEB服務器建立一個TCP連接
  • 瀏覽器給WEB服務器發送一個HTTP請求(GET/POST):一個HTTP請求報文由請求行(request line)、請求頭部(headers)、空行(blank line)和請求數據(request body)4個部分組成。
  • 服務端響應HTTP響應報文,報文由狀態行(status line)、相應頭部(headers)、空行(blank line)和響應數據(response body)4個部分組成。
  • 瀏覽器解析渲染
  • tcp和udp區別

  • TCP面向連接,UDP是無連接的,即發送數據之前不需要建立連接。
  • TCP提供可靠的服務。也就是說,通過TCP連接傳送的數據,無差錯,不丟失,不重復,且按序到達;UDP盡最大努力交付,即不保證可靠交付。
  • TCP面向字節流,實際上是TCP把數據看成一連串無結構的字節流,UDP是面向報文的,UDP沒有擁塞控制,因此網絡出現擁塞不會使源主機的發送速率降低(對實時應用很有用,如IP電話,實時視頻會議等)
  • 每一條TCP連接只能是點到點的,UDP支持一對一,一對多,多對一和多對多的交互通信。
  • TCP首部開銷20字節,UDP的首部開銷小,只有8個字節。
  • TCP的邏輯通信信道是全雙工的可靠信道,UDP則是不可靠信道。
  • tcp和udp的優點

    • TCP的優點: 可靠,穩定 TCP的可靠體現在TCP在傳遞數據之前,會有三次握手來建立連接,而且在數據傳遞時,有確認、窗口、重傳、擁塞控制機制,在數據傳完后,還會斷開連接用來節約系統資源。
    • TCP的缺點: 慢,效率低,占用系統資源高,易被攻擊 TCP在傳遞數據之前,要先建連接,這會消耗時間,而且在數據傳遞時,確認機制、重傳機制、擁塞控制機制等都會消耗大量的時間,而且要在每臺設備上維護所有的傳輸連接,事實上,每個連接都會占用系統的CPU、內存等硬件資源。 而且,因為TCP有確認機制、三次握手機制,這些也導致TCP容易被人利用,實現DOS、DDOS、CC等攻擊。
    • UDP的優點: 快,比TCP稍安全 UDP沒有TCP的握手、確認、窗口、重傳、擁塞控制等機制,UDP是一個無狀態的傳輸協議,所以它在傳遞數據時非常快。沒有TCP的這些機制,UDP較TCP被攻擊者利用的漏洞就要少一些。但UDP也是無法避免攻擊的,比如:UDP Flood攻擊……
    • UDP的缺點: 不可靠,不穩定 因為UDP沒有TCP那些可靠的機制,在數據傳遞時,如果網絡質量不好,就會很容易丟包。
    • 基于上面的優缺點,那么: 什么時候應該使用TCP: 當對網絡通訊質量有要求的時候,比如:整個數據要準確無誤的傳遞給對方,這往往用于一些要求可靠的應用,比如HTTP、HTTPS、FTP等傳輸文件的協議,POP、SMTP等郵件傳輸的協議。 在日常生活中,常見使用TCP協議的應用如下: 瀏覽器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件傳輸。什么時候應該使用UDP: 當對網絡通訊質量要求不高的時候,要求網絡通訊速度能盡量的快,這時就可以使用UDP。 比如,日常生活中,常見使用UDP協議的應用如下: QQ語音 QQ視頻 TFTP。

    三次握手

    • 第一次握手:建立連接時,客戶端發送syn包(syn=x)到服務器,并進入SYN_SENT狀態,等待服務器確認;SYN:同步序列編號(Synchronize Sequence Numbers)。
    • 第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=x+1),同時自己也發送一個SYN包(syn=y),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
    • 第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=y+1),此包發送完畢,客戶端和服務器進入ESTABLISHED(TCP連接成功)狀態,完成三次握手。

    為什么不能兩次握手

    • TCP是一個雙向通信協議,通信雙方都有能力發送信息,并接收響應。如果只是兩次握手, 至多只有連接發起方的起始序列號能被確認, 另一方選擇的序列號則得不到確認。

    • 這主要是為了防止已失效的請求連接報文忽然又傳送到了,從而產生錯誤。
      假定A向B發送一個連接請求,由于一些原因,導致A發出的連接請求在一個網絡節點逗留了比較多的時間。此時A會將此連接請求作為無效處理 又重新向B發起了一次新的連接請求,B正常收到此連接請求后建立了連接,數據傳輸完成后釋放了連接。如果此時A發出的第一次請求又到達了B,B會以為A又發起了一次連接請求,如果是兩次握手:此時連接就建立了,B會一直等待A發送數據,從而白白浪費B的資源。 如果是三次握手:由于A沒有發起連接請求,也就不會理會B的連接響應,B沒有收到A的確認連接,就會關閉掉本次連接。

    四次揮手

  • 客戶端進程發出連接釋放報文,并且停止發送數據。釋放數據報文首部,FIN=1,其序列號為seq=u(等于前面已經傳送過來的數據的最后一個字節的序號加1),此時,客戶端進入FIN-WAIT-1(終止等待1)狀態。 TCP規定,FIN報文段即使不攜帶數據,也要消耗一個序號。
  • 服務器收到連接釋放報文,發出確認報文,ACK=1,ack=u+1,并且帶上自己的序列號seq=v,此時,服務端就進入了CLOSE-WAIT(關閉等待)狀態。TCP服務器通知高層的應用進程,客戶端向服務器的方向就釋放了,這時候處于半關閉狀態,即客戶端已經沒有數據要發送了,但是服務器若發送數據,客戶端依然要接受。這個狀態還要持續一段時間,也就是整個CLOSE-WAIT狀態持續的時間。
  • 客戶端收到服務器的確認請求后,此時,客戶端就進入FIN-WAIT-2(終止等待2)狀態,等待服務器發送連接釋放報文(在這之前還需要接受服務器發送的最后的數據)。
  • 服務器將最后的數據發送完畢后,就向客戶端發送連接釋放報文,FIN=1,ack=u+1,由于在半關閉狀態,服務器很可能又發送了一些數據,假定此時的序列號為seq=w,此時,服務器就進入了LAST-ACK(最后確認)狀態,等待客戶端的確認。
  • 客戶端收到服務器的連接釋放報文后,必須發出確認,ACK=1,ack=w+1,而自己的序列號是seq=u+1,此時,客戶端就進入了TIME-WAIT(時間等待)狀態。注意此時TCP連接還沒有釋放,必須經過2??MSL(最長報文段壽命)的時間后,當客戶端撤銷相應的TCB后,才進入CLOSED狀態。
  • 服務器只要收到了客戶端發出的確認,立即進入CLOSED狀態。同樣,撤銷TCB后,就結束了這次的TCP連接。可以看到,服務器結束TCP連接的時間要比客戶端早一些。
  • 為什么連接的時候是三次握手,關閉的時候卻是四次握手

    因為當Server端收到Client端的SYN連接請求報文后,可以直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時,當Server端收到FIN報文時,很可能并不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴Client端,“你發的FIN報文我收到了”。只有等到我Server端所有的報文都發送完了,我才能發送FIN報文,因此不能一起發送。故需要四步握手。

    其他

    高并發系統的設計與實現

    在開發高并發系統時有三把利器用來保護系統:緩存、降級和限流。

    • 緩存:緩存比較好理解,在大型高并發系統中,如果沒有緩存數據庫將分分鐘被爆,系統也會瞬間癱瘓。使用緩存不單單能夠提升系統訪問速度、提高并發訪問量,也是保護數據庫、保護系統的有效方式。大型網站一般主要是“讀”,緩存的使用很容易被想到。在大型“寫”系統中,緩存也常常扮演者非常重要的角色。比如累積一些數據批量寫入,內存里面的緩存隊列(生產消費),以及HBase寫數據的機制等等也都是通過緩存提升系統的吞吐量或者實現系統的保護措施。甚至消息中間件,你也可以認為是一種分布式的數據緩存。
    • 降級:服務降級是當服務器壓力劇增的情況下,根據當前業務情況及流量對一些服務和頁面有策略的降級,以此釋放服務器資源以保證核心任務的正常運行。降級往往會指定不同的級別,面臨不同的異常等級執行不同的處理。根據服務方式:可以拒接服務,可以延遲服務,也有時候可以隨機服務。根據服務范圍:可以砍掉某個功能,也可以砍掉某些模塊。總之服務降級需要根據不同的業務需求采用不同的降級策略。主要的目的就是服務雖然有損但是總比沒有好。
    • 限流:限流可以認為服務降級的一種,限流就是限制系統的輸入和輸出流量已達到保護系統的目的。一般來說系統的吞吐量是可以被測算的,為了保證系統的穩定運行,一旦達到的需要限制的閾值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延遲處理,拒絕處理,或者部分拒絕處理等等。

    高并發系統的限流如何實現

    常見的限流算法有計數器、漏桶和令牌桶算法。漏桶算法在分布式環境中消息中間件或者Redis都是可選的方案。發放令牌的頻率增加可以提升整體數據處理的速度,而通過每次獲取令牌的個數增加或者放慢令牌的發放速度和降低整體數據處理速度。而漏桶不行,因為它的流出速率是固定的,程序處理速度也是固定的。

    面試感受及評價

    除了外企,體驗最好的就是阿里。絕對的脫穎而出,無論是面試官的專業程度還是面試官對參與面試人員的態度都完全突出于其他公司。非常的尊重人,以及會引導我去作出正確的回答,唯一就是阿里的HR是非常強勢的,永遠有一票否決權。而有些公司面試官會故意誤導你,想方設法讓你說出錯誤的答案,并且有些態度極其傲慢,讓人感覺很不尊重人。這里點名批評面試體驗最差的兩家公司:美團和Boss直聘。

    外企的話,體驗都很好,但是我都還沒面試完,后面會更新的。微軟是英文面的,亞馬遜不是。這倆都是以算法為主,微軟除了算法還聊了操作系統和計算機網絡,亞馬遜聊了較長時間的項目細節。

    最后

    最后說下自己的情況,17年在京東實習,19年7月離職。正式工作時間很短,就一年(算實習兩年),而且19年有半年的時間準備考研所以有半年的空檔期,這也是為什么我被很多HR掛了的原因。雖然Offer沒拿幾個,但是一半多都面到HR面了,所以對于兩三年經驗的感覺整理的問題還是比較有代表性的。

    總結

    以上是生活随笔為你收集整理的京东18届一年半经验社招面经的全部內容,希望文章能夠幫你解決所遇到的問題。

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