圖解學習網站:https://xiaolincoding.com
大家好,我是小林。
這周已經分享過了幾個大廠后端開發(fā)面經,有不少同學反饋也先看看小廠的面經,想感受一下區(qū)別。
有些小廠面試不會問太多,主要就考察幾個問題,可能十多分鐘就結束了,但是也有一些小廠比較例外,可能會拷打你一個小時,面試考察題量相當于大廠的題量,但是這種情況還是比較少數,同學們也不需要有太大的壓力。
分析十多分鐘的小廠面經也沒什么意思,大家能學到的內容也比較有限,正好之前看到有同學面南京某小廠Java后端開發(fā)的時候,足足被拷打了近 30 題,還是蠻有壓力的,有些題目也是大廠會考察的內容。
面試問題不止非常有廣度,而且有些問題問的也很有深度,今天就讓我們來看看吧!
考察的知識點,我給大家羅列了一下:
- MySQL:索引、特性和關鍵字、存儲引起、索引數據結構Redis:線程模型、持久化機制、分布式鎖、應用場景Java:SpringBoot、JVM、volatile
MySQL
MySQL索引的數據結構
從數據結構的角度來看,MySQL 常見索引有 B+Tree 索引、HASH 索引、Full-Text 索引。
每一種存儲引擎支持的索引類型不一定相同,表中總結了 MySQL 常見的存儲引擎 InnoDB、MyISAM 和 Memory 分別支持的索引類型。
InnoDB 是在 MySQL 5.5 之后成為默認的 MySQL 存儲引擎,B+Tree 索引類型也是 MySQL 存儲引擎采用最多的索引類型。
MySQL索引底層的數據結構是B+樹。B+樹是一種多叉樹,葉子節(jié)點才存放數據,非葉子節(jié)點只存放索引,而且每個節(jié)點里的數據是按主鍵順序存放的。每一層父節(jié)點的索引值都會出現(xiàn)在下層子節(jié)點的索引值中,因此在葉子節(jié)點中,包括了所有的索引值信息,并且每一個葉子節(jié)點都有兩個指針,分別指向下一個葉子節(jié)點和上一個葉子節(jié)點,形成一個雙向鏈表。
主鍵索引的 B+樹 如圖所示:
MySQL有哪些存儲引擎
MySQL中常用的存儲引擎分別是:MyISAM存儲引擎、innoDB存儲引擎,他們的區(qū)別在于:
- 事務:InnoDB 支持事務,MyISAM 不支持事務,這是 MySQL 將默認存儲引擎從 MyISAM 變成 InnoDB 的重要原因之一。索引結構:InnoDB 是聚簇索引,MyISAM 是非聚簇索引。聚簇索引的文件存放在主鍵索引的葉子節(jié)點上,因此 InnoDB 必須要有主鍵,通過主鍵索引效率很高。但是輔助索引需要兩次查詢,先查詢到主鍵,然后再通過主鍵查詢到數據。因此,主鍵不應該過大,因為主鍵太大,其他索引也都會很大。而 MyISAM 是非聚簇索引,數據文件是分離的,索引保存的是數據文件的指針。主鍵索引和輔助索引是獨立的。鎖粒度:InnoDB 最小的鎖粒度是行鎖,MyISAM 最小的鎖粒度是表鎖。一個更新語句會鎖住整張表,導致其他查詢和更新都會被阻塞,因此并發(fā)訪問受限。count 的效率:InnoDB 不保存表的具體行數,執(zhí)行 select count(*) from table 時需要全表掃描。而MyISAM 用一個變量保存了整個表的行數,執(zhí)行上述語句時只需要讀出該變量即可,速度很快。
MySQL為什么用B+樹結構,和其他結構比的優(yōu)點
MySQL 是會將數據持久化在硬盤,而存儲功能是由 MySQL 存儲引擎實現(xiàn)的,所以討論 MySQL 使用哪種數據結構作為索引,實際上是在討論存儲引使用哪種數據結構作為索引,InnoDB 是 MySQL 默認的存儲引擎,它就是采用了 B+ 樹作為索引的數據結構。
要設計一個 MySQL 的索引數據結構,不僅僅考慮數據結構增刪改的時間復雜度,更重要的是要考慮磁盤 I/0 的操作次數。因為索引和記錄都是存放在硬盤,硬盤是一個非常慢的存儲設備,我們在查詢數據的時候,最好能在盡可能少的磁盤 I/0 的操作次數內完成。
二分查找樹雖然是一個天然的二分結構,能很好的利用二分查找快速定位數據,但是它存在一種極端的情況,每當插入的元素都是樹內最大的元素,就會導致二分查找樹退化成一個鏈表,此時查詢復雜度就會從 O(logn)降低為 O(n)。
為了解決二分查找樹退化成鏈表的問題,就出現(xiàn)了自平衡二叉樹,保證了查詢操作的時間復雜度就會一直維持在 O(logn) 。但是它本質上還是一個二叉樹,每個節(jié)點只能有 2 個子節(jié)點,隨著元素的增多,樹的高度會越來越高。
而樹的高度決定于磁盤 I/O 操作的次數,因為樹是存儲在磁盤中的,訪問每個節(jié)點,都對應一次磁盤 I/O 操作,也就是說樹的高度就等于每次查詢數據時磁盤 IO 操作的次數,所以樹的高度越高,就會影響查詢性能。
B 樹和 B+ 都是通過多叉樹的方式,會將樹的高度變矮,所以這兩個數據結構非常適合檢索存于磁盤中的數據。
B+Tree vs B Tree:
-
- B+Tree 只在葉子節(jié)點存儲數據,而 B 樹 的非葉子節(jié)點也要存儲數據,所以 B+Tree 的單個節(jié)點的數據量更小,在相同的磁盤 I/O 次數下,就能查詢更多的節(jié)點。另外,B+Tree 葉子節(jié)點采用的是雙鏈表連接,適合 MySQL 中常見的基于范圍的順序查找,而 B 樹無法做到這一點。
B+Tree vs 二叉樹:
-
- 對于有 N 個葉子節(jié)點的 B+Tree,其搜索復雜度為O(logdN),其中 d 表示節(jié)點允許的最大子節(jié)點個數為 d 個。在實際的應用當中, d 值是大于100的,這樣就保證了,即使數據達到千萬級別時,B+Tree 的高度依然維持在 3~4 層左右,也就是說一次數據查詢操作只需要做 3~4 次的磁盤 I/O 操作就能查詢到目標數據。而二叉樹的每個父節(jié)點的兒子節(jié)點個數只能是 2 個,意味著其搜索復雜度為 O(logN),這已經比 B+Tree 高出不少,因此二叉樹檢索到目標數據所經歷的磁盤 I/O 次數要更多。
B+Tree vs Hash:
- Hash 在做等值查詢的時候效率賊快,搜索復雜度為 O(1)。但是 Hash 表不適合做范圍查詢,它更適合做等值的查詢,這也是 B+Tree 索引要比 Hash 表索引有著更廣泛的適用場景的原因
MySQL的關鍵字in和exist
在MySQL中,IN
和 EXISTS
都是用來處理子查詢的關鍵詞,但它們在功能、性能和使用場景上有各自的特點和區(qū)別。
IN關鍵字
IN
用于檢查左邊的表達式是否存在于右邊的列表或子查詢的結果集中。如果存在,則IN
返回TRUE
,否則返回FALSE
。
語法結構:
SELECT?column_name(s)
FROM?table_name
WHERE?column_name?IN?(value1,?value2,?...);
或
SELECT?column_name(s)
FROM?table_name
WHERE?column_name?IN?(SELECT?column_name?FROM?another_table?WHERE?condition);
例子:
SELECT?*?FROM?Customers
WHERE?Country?IN?('Germany',?'France');
EXISTS關鍵字
EXISTS
用于判斷子查詢是否至少能返回一行數據。它不關心子查詢返回什么數據,只關心是否有結果。如果子查詢有結果,則EXISTS
返回TRUE
,否則返回FALSE
。語法結構:
SELECT?column_name(s)
FROM?table_name
WHERE?EXISTS?(SELECT?column_name?FROM?another_table?WHERE?condition);
例子:
SELECT?*?FROM?Customers
WHERE?EXISTS?(SELECT?1?FROM?Orders?WHERE?Orders.CustomerID?=?Customers.CustomerID);
區(qū)別與選擇
性能差異:在很多情況下,EXISTS
的性能優(yōu)于IN
,特別是當子查詢的表很大時。這是因為EXISTS
一旦找到匹配項就會立即停止查詢,而IN
可能會掃描整個子查詢結果集。
使用場景:如果子查詢結果集較小且不頻繁變動,IN
可能更直觀易懂。而當子查詢涉及外部查詢的每一行判斷,并且子查詢的效率較高時,EXISTS
更為合適。
NULL值處理:IN
能夠正確處理子查詢中包含NULL值的情況,而EXISTS
不受子查詢結果中NULL值的影響,因為它關注的是行的存在性,而不是具體值。
事務四大特性是什么?
原子性(Atomicity):一個事務中的所有操作,要么全部完成,要么全部不完成,不會結束在中間某個環(huán)節(jié),而且事務在執(zhí)行過程中發(fā)生錯誤,會被回滾到事務開始前的狀態(tài),就像這個事務從來沒有執(zhí)行過一樣,就好比買一件商品,購買成功時,則給商家付了錢,商品到手;購買失敗時,則商品在商家手中,消費者的錢也沒花出去。
一致性(Consistency):是指事務操作前和操作后,數據滿足完整性約束,數據庫保持一致性狀態(tài)。比如,用戶 A 和用戶 B 在銀行分別有 800 元和 600 元,總共 1400 元,用戶 A 給用戶 B 轉賬 200 元,分為兩個步驟,從 A 的賬戶扣除 200 元和對 B 的賬戶增加 200 元。一致性就是要求上述步驟操作后,最后的結果是用戶 A 還有 600 元,用戶 B 有 800 元,總共 1400 元,而不會出現(xiàn)用戶 A 扣除了 200 元,但用戶 B 未增加的情況(該情況,用戶 A 和 B 均為 600 元,總共 1200 元)。
隔離性(Isolation):數據庫允許多個并發(fā)事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務并發(fā)執(zhí)行時由于交叉執(zhí)行而導致數據的不一致,因為多個事務同時使用相同的數據時,不會相互干擾,每個事務都有一個完整的數據空間,對其他并發(fā)事務是隔離的。也就是說,消費者購買商品這個事務,是不影響其他消費者購買的。
持久性(Durability):事務處理結束后,對數據的修改就是永久的,即便系統(tǒng)故障也不會丟失。
InnoDB 引擎通過什么技術來保證事務的這四個特性的呢?
- 持久性是通過 redo log (重做日志)來保證的;原子性是通過 undo log(回滾日志) 來保證的;隔離性是通過 MVCC(多版本并發(fā)控制) 或鎖機制來保證的;一致性則是通過持久性+原子性+隔離性來保證;
Redis
Redis是單線程的還是多線程的,為什么是單線程的?有了解過其特性嗎
Redis在執(zhí)行指令時是單線程操作的,但是在實際運行過程中也出現(xiàn)多個線程并行運行的情況。
Redis 單線程指的是「接收客戶端請求->解析請求 ->進行數據讀寫等操作->發(fā)送數據給客戶端」這個過程是由一個線程(主線程)來完成的,這也是我們常說 Redis 是單線程的原因。
除此之外,雖然 Redis 的主要工作(網絡 I/O 和執(zhí)行命令)一直是單線程模型,但是在 Redis 6.0 版本之后,也采用了多個 I/O 線程來處理網絡請求,這是因為隨著網絡硬件的性能提升,Redis 的性能瓶頸有時會出現(xiàn)在網絡 I/O 的處理上。
所以為了提高網絡 I/O 的并行度,Redis 6.0 對于網絡 I/O 采用多線程來處理。但是對于命令的執(zhí)行,Redis 仍然使用單線程來處理,所以大家不要誤解 Redis 有多線程同時執(zhí)行命令。
Redis 官方表示,Redis 6.0 版本引入的多線程 I/O 特性對性能提升至少是一倍以上。
Redis 6.0 版本支持的 I/O 多線程特性,默認情況下 I/O 多線程只針對發(fā)送響應數據(write client socket),并不會以多線程的方式處理讀請求(read client socket)。要想開啟多線程處理客戶端讀請求,就需要把 Redis.conf 配置文件中的 io-threads-do-reads 配置項設為 yes。
//讀請求也使用io多線程
io-threads-do-reads?yes
同時, Redis.conf 配置文件中提供了 IO 多線程個數的配置項。
//?io-threads?N,表示啟用?N-1?個?I/O?多線程(主線程也算一個?I/O?線程)
io-threads?4
關于線程數的設置,官方的建議是如果為 4 核的 CPU,建議線程數設置為 2 或 3,如果為 8 核 CPU 建議線程數設置為 6,線程數一定要小于機器核數,線程數并不是越大越好。
因此, Redis 6.0 版本之后,Redis 在啟動的時候,默認情況下會額外創(chuàng)建 6 個線程(_這里的線程數不包括主線程_):
- Redis-server :Redis的主線程,主要負責執(zhí)行命令;bio_close_file、bio_aof_fsync、bio_lazy_free:三個后臺線程,分別異步處理關閉文件任務、AOF刷盤任務、釋放內存任務;io_thd_1、io_thd_2、io_thd_3:三個 I/O 線程,io-threads 默認是 4 ,所以會啟動 3(4-1)個 I/O 多線程,用來分擔 Redis 網絡 I/O 的壓力。
Redis有哪2種持久化方式,分別的優(yōu)缺點
Redis 的讀寫操作都是在內存中,所以 Redis 性能才會高,但是當 Redis 重啟后,內存中的數據就會丟失,那為了保證內存中的數據不會丟失,Redis 實現(xiàn)了數據持久化的機制,這個機制會把數據存儲到磁盤,這樣在 Redis 重啟就能夠從磁盤中恢復原有的數據。Redis 共有三種數據持久化的方式:
AOF 日志:每執(zhí)行一條寫操作命令,就把該命令以追加的方式寫入到一個文件里;
RDB 快照:將某一時刻的內存數據,以二進制的方式寫入磁盤;
AOF 日志是如何實現(xiàn)的?
Redis 在執(zhí)行完一條寫操作命令后,就會把該命令以追加的方式寫入到一個文件里,然后 Redis 重啟時,會讀取該文件記錄的命令,然后逐一執(zhí)行命令的方式來進行數據恢復。
我這里以「_set name xiaolin_」命令作為例子,Redis 執(zhí)行了這條命令后,記錄在 AOF 日志里的內容如下圖:Redis 提供了 3 種寫回硬盤的策略, 在 Redis.conf 配置文件中的 appendfsync 配置項可以有以下 3 種參數可填:
Always,這個單詞的意思是「總是」,所以它的意思是每次寫操作命令執(zhí)行完后,同步將 AOF 日志數據寫回硬盤;
Everysec,這個單詞的意思是「每秒」,所以它的意思是每次寫操作命令執(zhí)行完后,先將命令寫入到 AOF 文件的內核緩沖區(qū),然后每隔一秒將緩沖區(qū)里的內容寫回到硬盤;
No,意味著不由 Redis 控制寫回硬盤的時機,轉交給操作系統(tǒng)控制寫回的時機,也就是每次寫操作命令執(zhí)行完后,先將命令寫入到 AOF 文件的內核緩沖區(qū),再由操作系統(tǒng)決定何時將緩沖區(qū)內容寫回硬盤。
我也把這 3 個寫回策略的優(yōu)缺點總結成了一張表格:
RDB 快照是如何實現(xiàn)的呢?
因為 AOF 日志記錄的是操作命令,不是實際的數據,所以用 AOF 方法做故障恢復時,需要全量把日志都執(zhí)行一遍,一旦 AOF 日志非常多,勢必會造成 Redis 的恢復操作緩慢。為了解決這個問題,Redis 增加了 RDB 快照。
所謂的快照,就是記錄某一個瞬間東西,比如當我們給風景拍照時,那一個瞬間的畫面和信息就記錄到了一張照片。所以,RDB 快照就是記錄某一個瞬間的內存數據,記錄的是實際數據,而 AOF 文件記錄的是命令操作的日志,而不是實際的數據。因此在 Redis 恢復數據時, RDB 恢復數據的效率會比 AOF 高些,因為直接將 RDB 文件讀入內存就可以,不需要像 AOF 那樣還需要額外執(zhí)行操作命令的步驟才能恢復數據。
Redis 提供了兩個命令來生成 RDB 文件,分別是 save 和 bgsave,他們的區(qū)別就在于是否在「主線程」里執(zhí)行:
-
- 執(zhí)行了 save 命令,就會在主線程生成 RDB 文件,由于和執(zhí)行操作命令在同一個線程,所以如果寫入 RDB 文件的時間太長,
會阻塞主線程
-
- ;執(zhí)行了 bgsave 命令,會創(chuàng)建一個子進程來生成 RDB 文件,這樣可以
避免主線程的阻塞;
優(yōu)缺點
AOF:
優(yōu)點:首先,AOF提供了更好的數據安全性,因為它默認每接收到一個寫命令就會追加到文件末尾。即使Redis服務器宕機,也只會丟失最后一次寫入前的數據。其次,AOF支持多種同步策略(如everysec、always等),可以根據需要調整數據安全性和性能之間的平衡。同時,AOF文件在Redis啟動時可以通過重寫機制優(yōu)化,減少文件體積,加快恢復速度。并且,即使文件發(fā)生損壞,AOF還提供了redis-check-aof工具來修復損壞的文件。
缺點:因為記錄了每一個寫操作,所以AOF文件通常比RDB文件更大,消耗更多的磁盤空間。并且,頻繁的磁盤IO操作(尤其是同步策略設置為always時)可能會對Redis的寫入性能造成一定影響。而且,當問個文件體積過大時,AOF會進行重寫操作,AOF如果沒有開啟AOF重寫或者重寫頻率較低,恢復過程可能較慢,因為它需要重放所有的操作命令。
RDB:
優(yōu)點: RDB通過快照的形式保存某一時刻的數據狀態(tài),文件體積小,備份和恢復的速度非???。并且,RDB是在主線程之外通過fork子進程來進行的,不會阻塞服務器處理命令請求,對Redis服務的性能影響較小。最后,由于是定期快照,RDB文件通常比AOF文件小得多。
缺點: RDB方式在兩次快照之間,如果Redis服務器發(fā)生故障,這段時間的數據將會丟失。并且,如果在RDB創(chuàng)建快照到恢復期間有寫操作,恢復后的數據可能與故障前的數據不完全一致
Redis除了緩存,還有哪些應用
Redis實現(xiàn)消息隊列
使用Pub/Sub模式:
-
- Redis的Pub/Sub是一種基于發(fā)布/訂閱的消息模式,任何客戶端都可以訂閱一個或多個頻道,發(fā)布者可以向特定頻道發(fā)送消息,所有訂閱該頻道的客戶端都會收到此消息。該方式實現(xiàn)起來比較簡單,發(fā)布者和訂閱者完全解耦,支持模式匹配訂閱。但是這種方式不支持消息持久化,消息發(fā)布后若無訂閱者在線則會被丟棄;不保證消息的順序和可靠性傳輸。
使用List結構
-
-
-
- :
- 使用List的方式通常是使用LPUSH命令將消息推入一個列表,消費者使用BLPOP或BRPOP阻塞地從列表中取出消息(先進先出FIFO)。這種方式可以實現(xiàn)簡單的任務隊列。這種方式可以結合Redis的過期時間特性實現(xiàn)消息的TTL;通過Redis事務可以保證操作的原子性。但是需要客戶端自己實現(xiàn)消息確認、重試等機制,相比專門的消息隊列系統(tǒng)功能較弱。
-
-
Redis實現(xiàn)分布式鎖
set nx方式:Redis提供了幾種方式來實現(xiàn)分布式鎖,最常用的是基于SET
命令的爭搶鎖機制。客戶端可以使用SET resource_name lock_value NX PX milliseconds
命令設置鎖,其中NX表示只有當鍵不存在時才設置,PX指定鎖的有效時間(毫秒)。如果設置成功,則認為客戶端獲得鎖??蛻舳送瓿刹僮骱?,解鎖的還需要先判斷鎖是不是自己,再進行刪除,這里涉及到 2 個操作,為了保證這兩個操作的原子性,可以用 lua 腳本來實現(xiàn)。
RedLock算法:
- 為了提高分布式鎖的可靠性,Redis作者Antirez提出了RedLock算法,它基于多個獨立的Redis實例來實現(xiàn)一個更安全的分布式鎖。它的基本原理是客戶端嘗試在多數(大于半數)Redis實例上同時加鎖,只有當在大多數實例上加鎖成功時才認為獲取鎖成功。鎖的超時時間應該遠小于單個實例的超時時間,以避免死鎖。該方式可以通過跨多個節(jié)點減少單點故障的影響,提高了鎖的可用性和安全性。
Redis分布式鎖的實現(xiàn),什么場景下用到分布式鎖
分布式鎖是用于分布式環(huán)境下并發(fā)控制的一種機制,用于控制某個資源在同一時刻只能被一個應用所使用。如下圖所示:
Redis 本身可以被多個客戶端共享訪問,正好就是一個共享存儲系統(tǒng),可以用來保存分布式鎖,而且 Redis 的讀寫性能高,可以應對高并發(fā)的鎖操作場景。Redis 的 SET 命令有個 NX 參數可以實現(xiàn)「key不存在才插入」,所以可以用它來實現(xiàn)分布式鎖:
- 如果 key 不存在,則顯示插入成功,可以用來表示加鎖成功;如果 key 存在,則會顯示插入失敗,可以用來表示加鎖失敗。
基于 Redis 節(jié)點實現(xiàn)分布式鎖時,對于加鎖操作,我們需要滿足三個條件。
- 加鎖包括了讀取鎖變量、檢查鎖變量值和設置鎖變量值三個操作,但需要以原子操作的方式完成,所以,我們使用 SET 命令帶上 NX 選項來實現(xiàn)加鎖;鎖變量需要設置過期時間,以免客戶端拿到鎖后發(fā)生異常,導致鎖一直無法釋放,所以,我們在 SET 命令執(zhí)行時加上 EX/PX 選項,設置其過期時間;鎖變量的值需要能區(qū)分來自不同客戶端的加鎖操作,以免在釋放鎖時,出現(xiàn)誤釋放操作,所以,我們使用 SET 命令設置鎖變量值時,每個客戶端設置的值是一個唯一值,用于標識客戶端;
滿足這三個條件的分布式命令如下:
SET?lock_key?unique_value?NX?PX?10000
- lock_key 就是 key 鍵;unique_value 是客戶端生成的唯一的標識,區(qū)分來自不同客戶端的鎖操作;NX 代表只在 lock_key 不存在時,才對 lock_key 進行設置操作;PX 10000 表示設置 lock_key 的過期時間為 10s,這是為了避免客戶端發(fā)生異常而無法釋放鎖。
而解鎖的過程就是將 lock_key 鍵刪除(del lock_key),但不能亂刪,要保證執(zhí)行操作的客戶端就是加鎖的客戶端。所以,解鎖的時候,我們要先判斷鎖的 unique_value 是否為加鎖客戶端,是的話,才將 lock_key 鍵刪除。
可以看到,解鎖是有兩個操作,這時就需要 Lua 腳本來保證解鎖的原子性,因為 Redis 在執(zhí)行 Lua 腳本時,可以以原子性的方式執(zhí)行,保證了鎖釋放操作的原子性。
//?釋放鎖時,先比較?unique_value?是否相等,避免鎖的誤釋放
if?redis.call("get",KEYS[1])?==?ARGV[1]?then
??return?redis.call("del",KEYS[1])
else
??return?0
end
這樣一來,就通過使用 SET 命令和 Lua 腳本在 Redis 單節(jié)點上完成了分布式鎖的加鎖和解鎖。
Java
SpringBoot自動裝配原理是什么?
SpringBoot 的自動裝配原理是基于Spring Framework的條件化配置和@EnableAutoConfiguration注解實現(xiàn)的。這種機制允許開發(fā)者在項目中引入相關的依賴,SpringBoot 將根據這些依賴自動配置應用程序的上下文和功能。
SpringBoot 定義了一套接口規(guī)范,這套規(guī)范規(guī)定:SpringBoot 在啟動時會掃描外部引用 jar 包中的META-INF/spring.factories文件,將文件中配置的類型信息加載到 Spring 容器(此處涉及到 JVM 類加載機制與 Spring 的容器知識),并執(zhí)行類中定義的各種操作。對于外部 jar 來說,只需要按照 SpringBoot 定義的標準,就能將自己的功能裝置進 SpringBoot。
通俗來講,自動裝配就是通過注解或一些簡單的配置就可以在SpringBoot的幫助下開啟和配置各種功能,比如數據庫訪問、Web開發(fā)。
SpringBoot自動裝配原理
首先點進@SpringBootApplication注解的內部
接下來將逐個解釋這些注解的作用:
- @Target({ElementType.TYPE}): 該注解指定了這個注解可以用來標記在類上。在這個特定的例子中,這表示該注解用于標記配置類。@Retention(RetentionPolicy.RUNTIME): 這個注解指定了注解的生命周期,即在運行時保留。這是因為 Spring Boot 在運行時掃描類路徑上的注解來實現(xiàn)自動配置,所以這里使用了 RUNTIME 保留策略。@Documented: 該注解表示這個注解應該被包含在 Java 文檔中。它是用于生成文檔的標記,使開發(fā)者能夠看到這個注解的相關信息。@Inherited: 這個注解指示一個被標注的類型是被繼承的。在這個例子中,它表明這個注解可以被繼承,如果一個類繼承了帶有這個注解的類,它也會繼承這個注解。@SpringBootConfiguration: 這個注解表明這是一個 Spring Boot 配置類。如果點進這個注解內部會發(fā)現(xiàn)與標準的 @Configuration 沒啥區(qū)別,只是為了表明這是一個專門用于 SpringBoot 的配置。@EnableAutoConfiguration: 這個注解是 Spring Boot 自動裝配的核心。它告訴 Spring oot 啟用自動配置機制,根據項目的依賴和配置自動配置應用程序的上下文。通過這個注解,SpringBoot 將嘗試根據類路徑上的依賴自動配置應用程序。@ComponentScan: 這個注解用于配置組件掃描的規(guī)則。在這里,它告訴 SpringBoot 在指定的包及其子包中查找組件,這些組件包括被注解的類、@Component 注解的類等。其中的 excludeFilters 參數用于指定排除哪些組件,這里使用了兩個自定義的過濾器,分別是 TypeExcludeFilter 和 AutoConfigurationExcludeFilter。
@EnableAutoConfiguration
這個注解是實現(xiàn)自動裝配的核心注解
- @AutoConfigurationPackage,將項目src中main包下的所有組件注冊到容器中,例如標注了Component注解的類等@Import({AutoConfigurationImportSelector.class}),是自動裝配的核心,接下來分析一下這個注解
AutoConfigurationImportSelector
AutoConfigurationImportSelector 是 Spring Boot 中一個重要的類,它實現(xiàn)了 ImportSelector 接口,用于實現(xiàn)自動配置的選擇和導入。具體來說,它通過分析項目的類路徑和條件來決定應該導入哪些自動配置類。代碼太多,選取部分主要功能的代碼
public?class?AutoConfigurationImportSelector?implements?DeferredImportSelector,?BeanClassLoaderAware,
??ResourceLoaderAware,?BeanFactoryAware,?EnvironmentAware,?Ordered?{
????
????//?...?(其他方法和屬性)
??//?獲取所有符合條件的類的全限定類名,例如RedisTemplate的全限定類名(org.springframework.data.redis.core.RedisTemplate;),這些類需要被加載到 IoC 容器中。
?@Override
?public?String[]?selectImports(AnnotationMetadata?annotationMetadata)?{
??//?掃描類路徑上的?META-INF/spring.factories?文件,獲取所有實現(xiàn)了?AutoConfiguration?接口的自動配置類
??List<String>?configurations?=?getCandidateConfigurations(annotationMetadata,?attributes);
??//?過濾掉不滿足條件的自動配置類,比如一些自動裝配類
??configurations?=?filter(configurations,?annotationMetadata,?attributes);
??//?排序自動配置類,根據?@AutoConfigureOrder?和?@AutoConfigureAfter/@AutoConfigureBefore?注解指定的順序
??sort(configurations,?annotationMetadata,?attributes);
??//?將滿足條件的自動配置類的類名數組返回,這些類將被導入到應用程序上下文中
??return?StringUtils.toStringArray(configurations);
?}
?//?...?(其他方法)
?protected?List<String>?getCandidateConfigurations(AnnotationMetadata?metadata,?AnnotationAttributes?attributes)?{
??//?獲取自動配置類的候選列表,從?META-INF/spring.factories?文件中讀取
??//?通過類加載器加載所有候選類
??List<String>?configurations?=?SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
????getBeanClassLoader());
??//?過濾出實現(xiàn)了?AutoConfiguration?接口的自動配置類
??configurations?=?configurations.stream()
????.filter(this::isEnabled)
????.collect(Collectors.toList());
??//?對于?Spring?Boot?1.x?版本,還需要添加?spring-boot-autoconfigure?包中的自動配置類
??//?configurations.addAll(getAutoConfigEntry(getAutoConfigurationEntry(metadata)));
??return?configurations;
?}
?//?...?(其他方法)
?protected?List<String>?filter(List<String>?configurations,?AnnotationMetadata?metadata,
???AnnotationAttributes?attributes)?{
??//?使用條件判斷機制,過濾掉不滿足條件的自動配置類
??configurations?=?configurations.stream()
????.filter(configuration?->?isConfigurationCandidate(configuration,?metadata,?attributes))
????.collect(Collectors.toList());
??return?configurations;
?}
?//?...?(其他方法)
?protected?void?sort(List<String>?configurations,?AnnotationMetadata?metadata,
???AnnotationAttributes?attributes)?{
??//?根據?@AutoConfigureOrder?和?@AutoConfigureAfter/@AutoConfigureBefore?注解指定的順序對自動配置類進行排序
??configurations.sort((o1,?o2)?->?{
???int?i1?=?getAutoConfigurationOrder(o1,?metadata,?attributes);
???int?i2?=?getAutoConfigurationOrder(o2,?metadata,?attributes);
???return?Integer.compare(i1,?i2);
??});
?}
??
???//?...?(其他方法)
}
梳理一下,以下是AutoConfigurationImportSelector
的主要工作:
-
- 掃描類路徑: 在應用程序啟動時,
AutoConfigurationImportSelector
-
- 會掃描類路徑上的
META-INF/spring.factories
-
- 文件,這個文件中包含了各種 Spring 配置和擴展的定義。在這里,它會查找所有實現(xiàn)了
AutoConfiguration
-
- 接口的類,具體的實現(xiàn)為
getCandidateConfigurations
-
- 方法。條件判斷: 對于每一個發(fā)現(xiàn)的自動配置類,
AutoConfigurationImportSelector
-
- 會使用條件判斷機制(通常是通過
@ConditionalOnXxx
- 注解)來確定是否滿足導入條件。這些條件可以是配置屬性、類是否存在、Bean是否存在等等。根據條件導入自動配置類: 滿足條件的自動配置類將被導入到應用程序的上下文中。這意味著它們會被實例化并應用于應用程序的配置。
說幾個啟動器(starter)?
spring-boot-starter-web:這是最常用的起步依賴之一,它包含了Spring MVC和Tomcat嵌入式服務器,用于快速構建Web應用程序。
spring-boot-starter-security:提供了Spring Security的基本配置,幫助開發(fā)者快速實現(xiàn)應用的安全性,包括認證和授權功能。
mybatis-spring-boot-starter:這個Starter是由MyBatis團隊提供的,用于簡化在Spring Boot應用中集成MyBatis的過程。它自動配置了MyBatis的相關組件,包括SqlSessionFactory、MapperScannerConfigurer等,使得開發(fā)者能夠快速地開始使用MyBatis進行數據庫操作。
spring-boot-starter-data-jpa或spring-boot-starter-jdbc:如果使用的是Java Persistence API (JPA)進行數據庫操作,那么應該使用spring-boot-starter-data-jpa。這個Starter包含了Hibernate等JPA實現(xiàn)以及數據庫連接池等必要的庫,可以讓你輕松地與MySQL數據庫進行交互。你需要在application.properties或application.yml中配置MySQL的連接信息。如果傾向于直接使用JDBC而不通過JPA,那么可以使用spring-boot-starter-jdbc,它提供了基本的JDBC支持。
spring-boot-starter-data-redis:用于集成Redis緩存和數據存儲服務。這個Starter包含了與Redis交互所需的客戶端(默認是Jedis客戶端,也可以配置為Lettuce客戶端),以及Spring Data Redis的支持,使得在Spring Boot應用中使用Redis變得非常便捷。同樣地,需要在配置文件中設置Redis服務器的連接詳情。
spring-boot-starter-test:包含了單元測試和集成測試所需的庫,如JUnit, Spring Test, AssertJ等,便于進行測試驅動開發(fā)(TDD)。
怎么搭建SpringBoot項目的?
用 IntelliJ IDEA工具開發(fā)的SpringBoot項目
voliatle關鍵字有什么作用?
volatite作用有 2 個:
保證變量對所有線程的可見性。當一個變量被聲明為volatile時,它會保證對這個變量的寫操作會立即刷新到主存中,而對這個變量的讀操作會直接從主存中讀取,從而確保了多線程環(huán)境下對該變量訪問的可見性。這意味著一個線程修改了volatile變量的值,其他線程能夠立刻看到這個修改,不會受到各自線程工作內存的影響。
禁止指令重排序優(yōu)化。volatile關鍵字在Java中主要通過內存屏障來禁止特定類型的指令重排序。
-
-
-
- 1)
寫-寫(Write-Write)屏障
-
-
-
-
- :在對volatile變量執(zhí)行寫操作之前,會插入一個寫屏障。這確保了在該變量寫操作之前的所有普通寫操作都已完成,防止了這些寫操作被移到volatile寫操作之后。
-
-
- 2)
讀-寫(Read-Write)屏障
-
-
-
-
- :在對volatile變量執(zhí)行讀操作之后,會插入一個讀屏障。它確保了對volatile變量的讀操作之后的所有普通讀操作都不會被提前到volatile讀之前執(zhí)行,保證了讀取到的數據是最新的。
-
-
- 3)
寫-讀(Write-Read)屏障
-
-
-
- :這是最重要的一個屏障,它發(fā)生在volatile寫之后和volatile讀之前。這個屏障確保了volatile寫操作之前的所有內存操作(包括寫操作)都不會被重排序到volatile讀之后,同時也確保了volatile讀操作之后的所有內存操作(包括讀操作)都不會被重排序到volatile寫之前。
JVM內存共享區(qū)域有哪些?
根據 JVM8 規(guī)范,JVM 運行時內存共分為虛擬機棧、堆、元空間、程序計數器、本地方法棧五個部分。還有一部分內存叫直接內存,屬于操作系統(tǒng)的本地內存,也是可以直接操作的。
JVM的內存結構主要分為以下幾個部分:
元空間
-
- :元空間并不是在堆上分配的,而是在堆外空間進行分配的,它的大小默認沒有上限,我們常說的方法區(qū),就在元空間中。元空間的本質和永久代類似,都是對JVM規(guī)范中方法區(qū)的實現(xiàn)。不過元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機中,而是使用本地內存。
Java 虛擬機棧
-
- :每個線程有一個私有的棧,隨著線程的創(chuàng)建而創(chuàng)建。棧里面存著的是一種叫“棧幀”的東西,每個方法會創(chuàng)建一個棧幀,棧幀中存放了局部變量表(基本數據類型和對象引用)、操作數棧、方法出口等信息。棧的大小可以固定也可以動態(tài)擴展。
本地方法棧
-
- :與虛擬機棧類似,區(qū)別是虛擬機棧執(zhí)行java方法,本地方法站執(zhí)行native方法。在虛擬機規(guī)范中對本地方法棧中方法使用的語言、使用方法與數據結構沒有強制規(guī)定,因此虛擬機可以自由實現(xiàn)它。
程序計數器:
-
- 程序計數器可以看成是當前線程所執(zhí)行的字節(jié)碼的行號指示器。在任何一個確定的時刻,一個處理器(對于多內核來說是一個內核)都只會執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復到正確的執(zhí)行位置,每條線程都需要一個獨立的程序計數器,我們稱這類內存區(qū)域為“線程私有”內存。
-
- :堆內存是 JVM 所有線程共享的部分,在虛擬機啟動的時候就已經創(chuàng)建。所有的對象和數組都在堆上進行分配。這部分空間可通過 GC 進行回收。當申請不到空間時會拋出 OutOfMemoryError。堆是JVM內存占用最大,管理最復雜的一個區(qū)域。其唯一的用途就是存放對象實例:所有的對象實例及數組都在對上進行分配。jdk1.8后,字符串常量池從永久代中剝離出來,存放在隊中。
直接內存
- :直接內存并不是虛擬機運行時數據區(qū)的一部分,也不是Java 虛擬機規(guī)范中農定義的內存區(qū)域。在JDK1.4 中新加入了NIO(New Input/Output)類,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O 方式,它可以使用native 函數庫直接分配堆外內存,然后通脫一個存儲在Java堆中的DirectByteBuffer 對象作為這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在Java堆和Native堆中來回復制數據。
方法區(qū)中的方法的執(zhí)行過程?
當程序中通過對象或類直接調用某個方法時,主要包括以下幾個步驟:
解析方法調用:JVM會根據方法的符號引用找到實際的方法地址(如果之前沒有解析過的話)。
棧幀創(chuàng)建:在調用一個方法前,JVM會在當前線程的Java虛擬機棧中為該方法分配一個新的棧幀,用于存儲局部變量表、操作數棧、動態(tài)鏈接、方法出口等信息。
執(zhí)行方法:執(zhí)行方法內的字節(jié)碼指令,涉及的操作可能包括局部變量的讀寫、操作數棧的操作、跳轉控制、對象創(chuàng)建、方法調用等。
返回處理:方法執(zhí)行完畢后,可能會返回一個結果給調用者,并清理當前棧幀,恢復調用者的執(zhí)行環(huán)境。
方法區(qū)中還有哪些東西?
《深入理解Java虛擬機》書中對方法區(qū)(Method Area)存儲內容描述如下:它用于存儲已被虛擬機加載的類型信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼緩存等。
- 類信息:包括類的結構信息、類的訪問修飾符、父類與接口等信息。常量池:存儲類和接口中的常量,包括字面值常量、符號引用,以及運行時常量池。靜態(tài)變量:存儲類的靜態(tài)變量,這些變量在類初始化的時候被賦值。方法字節(jié)碼:存儲類的方法字節(jié)碼,即編譯后的代碼。符號引用:存儲類和方法的符號引用,是一種直接引用不同于直接引用的引用類型。運行時常量池:存儲著在類文件中的常量池數據,在類加載后在方法區(qū)生成該運行時常量池。常量池緩存:用于提升類加載的效率,將常用的常量緩存起來方便使用。
類加載器有哪些?
啟動類加載器(Bootstrap Class Loader)
-
- :這是最頂層的類加載器,負責加載Java的核心庫(如位于jre/lib/rt.jar中的類),它是用C++編寫的,是JVM的一部分。啟動類加載器無法被Java程序直接引用。
擴展類加載器(Extension Class Loader)
-
- :它是Java語言實現(xiàn)的,繼承自ClassLoader類,負責加載Java擴展目錄(jre/lib/ext或由系統(tǒng)變量java.ext.dirs指定的目錄)下的jar包和類庫。擴展類加載器由啟動類加載器加載,并且父加載器就是啟動類加載器。
系統(tǒng)類加載器(System Class Loader)/ 應用程序類加載器(Application Class Loader)
-
- :這也是Java語言實現(xiàn)的,負責加載用戶類路徑(ClassPath)上的指定類庫,是我們平時編寫Java程序時默認使用的類加載器。系統(tǒng)類加載器的父加載器是擴展類加載器。它可以通過ClassLoader.getSystemClassLoader()方法獲取到。
自定義類加載器(Custom Class Loader)
- :開發(fā)者可以根據需求定制類的加載方式,比如從網絡加載class文件、數據庫、甚至是加密的文件中加載類等。自定義類加載器可以用來擴展Java應用程序的靈活性和安全性,是Java動態(tài)性的一個重要體現(xiàn)。
這些類加載器之間的關系形成了雙親委派模型,其核心思想是當一個類加載器收到類加載的請求時,首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啟動類加載器中。
只有當父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。
雙親委派模型的作用
保證類的唯一性:通過委托機制,確保了所有加載請求都會傳遞到啟動類加載器,避免了不同類加載器重復加載相同類的情況,保證了Java核心類庫的統(tǒng)一性,也防止了用戶自定義類覆蓋核心類庫的可能。
保證安全性:由于Java核心庫被啟動類加載器加載,而啟動類加載器只加載信任的類路徑中的類,這樣可以防止不可信的類假冒核心類,增強了系統(tǒng)的安全性。例如,惡意代碼無法自定義一個java.lang.System類并加載到JVM中,因為這個請求會被委托給啟動類加載器,而啟動類加載器只會加載標準的Java庫中的類。
支持隔離和層次劃分:雙親委派模型支持不同層次的類加載器服務于不同的類加載需求,如應用程序類加載器加載用戶代碼,擴展類加載器加載擴展框架,啟動類加載器加載核心庫。這種層次化的劃分有助于實現(xiàn)沙箱安全機制,保證了各個層級類加載器的職責清晰,也便于維護和擴展。
簡化了加載流程:通過委派,大部分類能夠被正確的類加載器加載,減少了每個加載器需要處理的類的數量,簡化了類的加載過程,提高了加載效率。
講一下類加載過程?
類從被加載到虛擬機內存開始,到卸載出內存為止,它的整個生命周期包括以下 7 個階段:
加載:
-
- 通過類的全限定名(包名 + 類名),獲取到該類的.class文件的二進制字節(jié)流,將二進制字節(jié)流所代表的靜態(tài)存儲結構,轉化為方法區(qū)運行時的數據結構,在內存中生成一個代表該類的java.lang.Class對象,作為方法區(qū)這個類的各種數據的訪問入口
連接:
-
- 驗證、準備、解析 3 個階段統(tǒng)稱為連接。
-
- 驗證:確保class文件中的字節(jié)流包含的信息,符合當前虛擬機的要求,保證這個被加載的class類的正確性,不會危害到虛擬機的安全。驗證階段大致會完成以下四個階段的檢驗動作:文件格式校驗、元數據驗證、字節(jié)碼驗證、符號引用驗證準備:為類中的靜態(tài)字段分配內存,并設置默認的初始值,比如int類型初始值是0。被final修飾的static字段不會設置,因為final在編譯的時候就分配了
-
解析:解析階段是虛擬機將常量池的「符號引用」直接替換為「直接引用」的過程。符號引用是以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用的時候可以無歧義地定位到目標即可。直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄,直接引用是和虛擬機實現(xiàn)的內存布局相關的。如果有了直接引用, 那引用的目標必定已經存在在內存中了。
初始化:初始化是整個類加載過程的最后一個階段,初始化階段簡單來說就是執(zhí)行類的構造器方法(() ),要注意的是這里的構造器方法()并不是開發(fā)者寫的,而是編譯器自動生成的。
使用:使用類或者創(chuàng)建對象
卸載:如果有下面的情況,類就會被卸載:1. 該類所有的實例都已經被回收,也就是java堆中不存在該類的任何實例。2. 加載該類的ClassLoader已經被回收。3. 類對應的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射訪問該類的方法。
判斷垃圾的方法有哪些?
在Java中,判斷對象是否為垃圾(即不再被使用,可以被垃圾回收器回收)主要依據兩種主流的垃圾回收算法來實現(xiàn):引用計數法和可達性分析算法。
引用計數法(Reference Counting)
原理:為每個對象分配一個引用計數器,每當有一個地方引用它時,計數器加1;當引用失效時,計數器減1。當計數器為0時,表示對象不再被任何變量引用,可以被回收。
缺點:不能解決循環(huán)引用的問題,即兩個對象相互引用,但不再被其他任何對象引用,這時引用計數器不會為0,導致對象無法被回收。
可達性分析算法(Reachability Analysis)
Java虛擬機主要采用此算法來判斷對象是否為垃圾。
原理
- :從一組稱為GC Roots(垃圾收集根)的對象出發(fā),向下追溯它們引用的對象,以及這些對象引用的其他對象,以此類推。如果一個對象到GC Roots沒有任何引用鏈相連(即從GC Roots到這個對象不可達),那么這個對象就被認為是不可達的,可以被回收。GC Roots對象包括:虛擬機棧(棧幀中的本地變量表)中引用的對象、方法區(qū)中類靜態(tài)屬性引用的對象、本地方法棧中JNI(Java Native Interface)引用的對象、活躍線程的引用等。
垃圾回收算法有哪些?
標記-清除算法:標記-清除算法分為“標記”和“清除”兩個階段,首先通過可達性分析,標記出所有需要回收的對象,然后統(tǒng)一回收所有被標記的對象。標記-清除算法有兩個缺陷,一個是效率問題,標記和清除的過程效率都不高,另外一個就是,清除結束后會造成大量的碎片空間。有可能會造成在申請大塊內存的時候因為沒有足夠的連續(xù)空間導致再次 GC。
復制算法:為了解決碎片空間的問題,出現(xiàn)了“復制算法”。復制算法的原理是,將內存分成兩塊,每次申請內存時都使用其中的一塊,當內存不夠時,將這一塊內存中所有存活的復制到另一塊上。然后將然后再把已使用的內存整個清理掉。復制算法解決了空間碎片的問題。但是也帶來了新的問題。因為每次在申請內存時,都只能使用一半的內存空間。內存利用率嚴重不足。
標記-整理算法:復制算法在 GC 之后存活對象較少的情況下效率比較高,但如果存活對象比較多時,會執(zhí)行較多的復制操作,效率就會下降。而老年代的對象在 GC 之后的存活率就比較高,所以就有人提出了“標記-整理算法”。標記-整理算法的“標記”過程與“標記-清除算法”的標記過程一致,但標記之后不會直接清理。而是將所有存活對象都移動到內存的一端。移動結束后直接清理掉剩余部分。
分代回收算法:分代收集是將內存劃分成了新生代和老年代。分配的依據是對象的生存周期,或者說經歷過的 GC 次數。對象創(chuàng)建時,一般在新生代申請內存,當經歷一次 GC 之后如果對還存活,那么對象的年齡 +1。當年齡超過一定值(默認是 15,可以通過參數 -XX:MaxTenuringThreshold 來設定)后,如果對象還存活,那么該對象會進入老年代。
標記清除算法的缺點是什么?
主要缺點有兩個:
- 一個是效率問題,標記和清除過程的效率都不高;另外一個是空間問題,標記清除之后會產生大量不連續(xù)的內存碎片,空間碎片太多可能會導致,當程序在以后的運行過程中需要分配較大對象時無法找到足夠的連續(xù)內存而不得不提前觸發(fā)另一次垃圾收集動作。
堆分為哪幾部分呢?
Java堆(Heap)是Java虛擬機(JVM)中內存管理的一個重要區(qū)域,主要用于存放對象實例和數組。隨著JVM的發(fā)展和不同垃圾收集器的實現(xiàn),堆的具體劃分可能會有所不同,但通常可以分為以下幾個部分:
新生代(Young Generation):新生代分為Eden Space和Survivor Space。在Eden Space中, 大多數新創(chuàng)建的對象首先存放在這里。Eden區(qū)相對較小,當Eden區(qū)滿時,會觸發(fā)一次Minor GC(新生代垃圾回收)。在Survivor Spaces中,通常分為兩個相等大小的區(qū)域,稱為S0(Survivor 0)和S1(Survivor 1)。在每次Minor GC后,存活下來的對象會被移動到其中一個Survivor空間,以繼續(xù)它們的生命周期。這兩個區(qū)域輪流充當對象的中轉站,幫助區(qū)分短暫存活的對象和長期存活的對象。
老年代(Old Generation/Tenured Generation):存放過一次或多次Minor GC仍存活的對象會被移動到老年代。老年代中的對象生命周期較長,因此Major GC(也稱為Full GC,涉及老年代的垃圾回收)發(fā)生的頻率相對較低,但其執(zhí)行時間通常比Minor GC長。老年代的空間通常比新生代大,以存儲更多的長期存活對象。
元空間(Metaspace):從Java 8開始,永久代(Permanent Generation)被元空間取代,用于存儲類的元數據信息,如類的結構信息(如字段、方法信息等)。元空間并不在Java堆中,而是使用本地內存,這解決了永久代容易出現(xiàn)的內存溢出問題。
大對象區(qū)(Large Object Space / Humongous Objects):在某些JVM實現(xiàn)中(如G1垃圾收集器),為大對象分配了專門的區(qū)域,稱為大對象區(qū)或Humongous Objects區(qū)域。大對象是指需要大量連續(xù)內存空間的對象,如大數組。這類對象直接分配在老年代,以避免因頻繁的年輕代晉升而導致的內存碎片化問題。