全面解析Redis、JVM

一、Redis

1.  Redis 是什麼?都有哪些使用場景?

Redis 是一個使用 C 語言開發的高速緩存數據庫。

Redis 使用場景:

  • 記錄帖子點贊數、點擊數、評論數;

  • 緩存近期熱帖;

  • 緩存文章詳情信息;

  • 記錄用戶會話信息。

 

2. Redis 有哪些功能?

  • 數據緩存功能

  • 分佈式鎖的功能

  • 支持數據持久化

  • 支持事務

  • 支持消息隊列

 

3. Redis 和 memcache 有什麼區別?

  • 存儲方式不同:memcache 把數據全部存在內存之中,斷電後會掛掉,數據不能超過內存大小;Redis 有部份存在硬盤上,這樣能保證數據的持久性。

  • 數據支持類型:memcache 對數據類型支持相對簡單;Redis 有複雜的數據類型。

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

  • value 值大小不同:Redis 最大可以達到 1gb;memcache 只有 1mb。

 

4. Redis 爲什麼是單線程的?

因爲 cpu 不是 Redis 的瓶頸,Redis 的瓶頸最有可能是機器內存或者網絡帶寬。既然單線程容易實現,而且 cpu 又不會成爲瓶頸,那就順理成章地採用單線程的方案了。

關於 Redis 的性能,官方網站也有,普通筆記本輕鬆處理每秒幾十萬的請求。

而且單線程並不代表就慢 nginx 和 nodejs 也都是高性能單線程的代表。

 

5. 什麼是緩存穿透?怎麼解決?

緩存穿透:指查詢一個一定不存在的數據,由於緩存是不命中時需要從數據庫查詢,查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到數據庫去查詢,造成緩存穿透。

解決方案:最簡單粗暴的方法如果一個查詢返回的數據爲空(不管是數據不存在,還是系統故障),我們就把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘。

 

6. Redis 支持的數據類型有哪些?

Redis 支持的數據類型:string(字符串)、list(列表)、hash(字典)、set(集合)、zset(有序集合)。

 

7. Redis 支持的 Java 客戶端都有哪些?

支持的 Java 客戶端有 Redisson、jedis、lettuce 等。

 

8. jedis 和 Redisson 有哪些區別?

  • jedis:提供了比較全面的 Redis 命令的支持。

  • Redisson:實現了分佈式和可擴展的 Java 數據結構,與 jedis 相比 Redisson 的功能相對簡單,不支持排序、事務、管道、分區等 Redis 特性。

 

9. 怎麼保證緩存和數據庫數據的一致性?

  • 合理設置緩存的過期時間。

  • 新增、更改、刪除數據庫操作時同步更新 Redis,可以使用事物機制來保證數據的一致性。

 

10. Redis 持久化有幾種方式?

Redis 的持久化有兩種方式,或者說有兩種策略:

  • RDB(Redis Database):指定的時間間隔能對你的數據進行快照存儲。

  • AOF(Append Only File):每一個收到的寫命令都通過write函數追加到文件中。

 

11. Redis 怎麼實現分佈式鎖?

Redis 分佈式鎖其實就是在系統裏面佔一個“坑”,其他程序也要佔“坑”的時候,佔用成功了就可以繼續執行,失敗了就只能放棄或稍後重試。

佔坑一般使用 setnx(set if not exists)指令,只允許被一個程序佔有,使用完調用 del 釋放鎖。

 

12. Redis 分佈式鎖有什麼缺陷?

Redis 分佈式鎖不能解決超時的問題,分佈式鎖有一個超時時間,程序的執行如果超出了鎖的超時時間就會出現問題。

 

13. Redis 如何做內存優化?

儘量使用 Redis 的散列表,把相關的信息放到散列表裏面存儲,而不是把每個字段單獨存儲,這樣可以有效的減少內存使用。比如將 Web 系統的用戶對象,應該放到散列表裏面再整體存儲到 Redis,而不是把用戶的姓名、年齡、密碼、郵箱等字段分別設置 key 進行存儲。

 

14. Redis 淘汰策略有哪些?

  • volatile-lru:從已設置過期時間的數據集(server. db[i]. expires)中挑選最近最少使用的數據淘汰。

  • volatile-ttl:從已設置過期時間的數據集(server. db[i]. expires)中挑選將要過期的數據淘汰。

  • volatile-random:從已設置過期時間的數據集(server. db[i]. expires)中任意選擇數據淘汰。

  • allkeys-lru:從數據集(server. db[i]. dict)中挑選最近最少使用的數據淘汰。

  • allkeys-random:從數據集(server. db[i]. dict)中任意選擇數據淘汰。

  • no-enviction(驅逐):禁止驅逐數據。

 

15. Redis 常見的性能問題有哪些?該如何解決?

  • 主服務器寫內存快照,會阻塞主線程的工作,當快照比較大時對性能影響是非常大的,會間斷性暫停服務,所以主服務器最好不要寫內存快照。

  • Redis 主從複製的性能問題,爲了主從複製的速度和連接的穩定性,主從庫最好在同一個局域網內。

     

 

 

二、JVM

1.  說一下 JVM 的主要組成部分?及其作用?

  • 類加載器(ClassLoader)

  • 運行時數據區(Runtime Data Area)

  • 執行引擎(Execution Engine)

  • 本地庫接口(Native Interface)

組件的作用: 首先通過類加載器(ClassLoader)會把 Java 代碼轉換成字節碼,運行時數據區(Runtime Data Area)再把字節碼加載到內存中,而字節碼文件只是 JVM 的一套指令集規範,並不能直接交個底層操作系統去執行,因此需要特定的命令解析器執行引擎(Execution Engine),將字節碼翻譯成底層系統指令,再交由 CPU 去執行,而這個過程中需要調用其他語言的本地庫接口(Native Interface)來實現整個程序的功能。

 

2. 說一下 JVM 運行時數據區?

不同虛擬機的運行時數據區可能略微有所不同,但都會遵從 Java 虛擬機規範, Java 虛擬機規範規定的區域分爲以下 5 個部分:

  • 程序計數器(Program Counter Register):當前線程所執行的字節碼的行號指示器,字節碼解析器的工作是通過改變這個計數器的值,來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能,都需要依賴這個計數器來完成;

  • Java 虛擬機棧(Java Virtual Machine Stacks):用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息;

  • 本地方法棧(Native Method Stack):與虛擬機棧的作用是一樣的,只不過虛擬機棧是服務 Java 方法的,而本地方法棧是爲虛擬機調用 Native 方法服務的;

  • Java 堆(Java Heap):Java 虛擬機中內存最大的一塊,是被所有線程共享的,幾乎所有的對象實例都在這裏分配內存;

  • 方法區(Methed Area):用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯後的代碼等數據。

 

3. 說一下堆棧的區別?

  • 功能方面:堆是用來存放對象的,棧是用來執行程序的。

  • 共享性:堆是線程共享的,棧是線程私有的。

  • 空間大小:堆大小遠遠大於棧。

 

4. 隊列和棧是什麼?有什麼區別?

隊列和棧都是被用來預存儲數據的。

隊列允許先進先出檢索元素,但也有例外的情況,Deque 接口允許從兩端檢索元素。

棧和隊列很相似,但它運行對元素進行後進先出進行檢索。

 

5. 什麼是雙親委派模型?

在介紹雙親委派模型之前先說下類加載器。對於任意一個類,都需要由加載它的類加載器和這個類本身一同確立在 JVM 中的唯一性,每一個類加載器,都有一個獨立的類名稱空間。類加載器就是根據指定全限定名稱將 class 文件加載到 JVM 內存,然後再轉化爲 class 對象。

類加載器分類:

  • 啓動類加載器(Bootstrap ClassLoader),是虛擬機自身的一部分,用來加載Java_HOME/lib/目錄中的,或者被 -Xbootclasspath 參數所指定的路徑中並且被虛擬機識別的類庫;

  • 其他類加載器:

  • 擴展類加載器(Extension ClassLoader):負責加載\lib\ext目錄或Java. ext. dirs系統變量指定的路徑中的所有類庫;

  • 應用程序類加載器(Application ClassLoader)。負責加載用戶類路徑(classpath)上的指定類庫,我們可以直接使用這個類加載器。一般情況,如果我們沒有自定義類加載器默認就是用這個加載器。

雙親委派模型:如果一個類加載器收到了類加載的請求,它首先不會自己去加載這個類,而是把這個請求委派給父類加載器去完成,每一層的類加載器都是如此,這樣所有的加載請求都會被傳送到頂層的啓動類加載器中,只有當父加載無法完成加載請求(它的搜索範圍中沒找到所需的類)時,子加載器纔會嘗試去加載類。

 

6. 說一下類裝載的執行過程?

類裝載分爲以下 5 個步驟:

  • 加載:根據查找路徑找到相應的 class 文件然後導入;

  • 檢查:檢查加載的 class 文件的正確性;

  • 準備:給類中的靜態變量分配內存空間;

  • 解析:虛擬機將常量池中的符號引用替換成直接引用的過程。符號引用就理解爲一個標示,而在直接引用直接指向內存中的地址;

  • 初始化:對靜態變量和靜態代碼塊執行初始化工作。

 

7. 怎麼判斷對象是否可以被回收?

一般有兩種方法來判斷:

  • 引用計數器:爲每個對象創建一個引用計數,有對象引用時計數器 +1,引用被釋放時計數 -1,當計數器爲 0 時就可以被回收。它有一個缺點不能解決循環引用的問題;

  • 可達性分析:從 GC Roots 開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到 GC Roots 沒有任何引用鏈相連時,則證明此對象是可以被回收的。

 

8. Java 中都有哪些引用類型?

  • 強引用:發生 gc 的時候不會被回收。

  • 軟引用:有用但不是必須的對象,在發生內存溢出之前會被回收。

  • 弱引用:有用但不是必須的對象,在下一次GC時會被回收。

  • 虛引用(幽靈引用/幻影引用):無法通過虛引用獲得對象,用 PhantomReference 實現虛引用,虛引用的用途是在 gc 時返回一個通知。

 

9. 說一下 JVM 有哪些垃圾回收算法?

  • 標記-清除算法:標記無用對象,然後進行清除回收。缺點:效率不高,無法清除垃圾碎片。

  • 標記-整理算法:標記無用對象,讓所有存活的對象都向一端移動,然後直接清除掉端邊界以外的內存。

  • 複製算法:按照容量劃分二個大小相等的內存區域,當一塊用完的時候將活着的對象複製到另一塊上,然後再把已使用的內存空間一次清理掉。缺點:內存使用率不高,只有原來的一半。

  • 分代算法:根據對象存活週期的不同將內存劃分爲幾塊,一般是新生代和老年代,新生代基本採用複製算法,老年代採用標記整理算法。

 

10. 說一下 JVM 有哪些垃圾回收器?

  • Serial:最早的單線程串行垃圾回收器。

  • Serial Old:Serial 垃圾回收器的老年版本,同樣也是單線程的,可以作爲 CMS 垃圾回收器的備選預案。

  • ParNew:是 Serial 的多線程版本。

  • Parallel 和 ParNew 收集器類似是多線程的,但 Parallel 是吞吐量優先的收集器,可以犧牲等待時間換取系統的吞吐量。

  • Parallel Old 是 Parallel 老生代版本,Parallel 使用的是複製的內存回收算法,Parallel Old 使用的是標記-整理的內存回收算法。

  • CMS:一種以獲得最短停頓時間爲目標的收集器,非常適用 B/S 系統。

  • G1:一種兼顧吞吐量和停頓時間的 GC 實現,是 JDK 9 以後的默認 GC 選項。

 

11. 詳細介紹一下 CMS 垃圾回收器?

CMS 是英文 Concurrent Mark-Sweep 的簡稱,是以犧牲吞吐量爲代價來獲得最短回收停頓時間的垃圾回收器。對於要求服務器響應速度的應用上,這種垃圾回收器非常適合。在啓動 JVM 的參數加上“-XX:+UseConcMarkSweepGC”來指定使用 CMS 垃圾回收器。

CMS 使用的是標記-清除的算法實現的,所以在 gc 的時候回產生大量的內存碎片,當剩餘內存不能滿足程序運行要求時,系統將會出現 Concurrent Mode Failure,臨時 CMS 會採用 Serial Old 回收器進行垃圾清除,此時的性能將會被降低。

 

12. 新生代垃圾回收器和老生代垃圾回收器都有哪些?有什麼區別?

  • 新生代回收器:Serial、ParNew、Parallel Scavenge

  • 老年代回收器:Serial Old、Parallel Old、CMS

  • 整堆回收器:G1

新生代垃圾回收器一般採用的是複製算法,複製算法的優點是效率高,缺點是內存利用率低;老年代回收器一般採用的是標記-整理的算法進行垃圾回收。

 

13. 簡述分代垃圾回收器是怎麼工作的?

分代回收器有兩個分區:老生代和新生代,新生代默認的空間佔比總空間的 1/3,老生代的默認佔比是 2/3。

新生代使用的是複製算法,新生代裏有 3 個分區:Eden、To Survivor、From Survivor,它們的默認佔比是 8:1:1,它的執行流程如下:

  • 把 Eden + From Survivor 存活的對象放入 To Survivor 區;

  • 清空 Eden 和 From Survivor 分區;

  • From Survivor 和 To Survivor 分區交換,From Survivor 變 To Survivor,To Survivor 變 From Survivor。

每次在 From Survivor 到 To Survivor 移動時都存活的對象,年齡就 +1,當年齡到達 15(默認配置是 15)時,升級爲老生代。大對象也會直接進入老生代。

老生代當空間佔用到達某個值之後就會觸發全局垃圾收回,一般使用標記整理的執行算法。以上這些循環往復就構成了整個分代垃圾回收的整體執行流程。

 

14. 說一下 JVM 調優的工具?

JDK 自帶了很多監控工具,都位於 JDK 的 bin 目錄下,其中最常用的是 jconsole 和 jvisualvm 這兩款視圖監控工具。

  • jconsole:用於對 JVM 中的內存、線程和類等進行監控;

  • jvisualvm:JDK 自帶的全能分析工具,可以分析:內存快照、線程快照、程序死鎖、監控內存的變化、gc 變化等。

 

 

15. 常用的 JVM 調優的參數都有哪些?

  • -Xms2g:初始化推大小爲 2g;

  • -Xmx2g:堆最大內存爲 2g;

  • -XX:NewRatio=4:設置年輕的和老年代的內存比例爲 1:4;

  • -XX:SurvivorRatio=8:設置新生代 Eden 和 Survivor 比例爲 8:2;

  • –XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器組合;

  • -XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器組合;

  • -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器組合;

  • -XX:+PrintGC:開啓打印 gc 信息;

  • -XX:+PrintGCDetails:打印 gc 詳細信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章