Java面試題解析(事務+緩存+資料庫+多線程+JVM)

2020-12-16 圖靈學院

前言

題目答案總結並非標準,僅供參考,如果有錯誤或者更好的見解,歡迎留言討論!

事務

1、什麼是事務?事務的特性(ACID)

什麼是事務:事務是程序中一系列嚴密的操作,所有操作執行必須成功完成,否則在每個操作所做的更改將會被撤銷,這也是事務的原子性(要麼成功,要麼失敗)。

事務特性分為四個:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持續性(Durability)簡稱ACID。

1、原子性:事務是資料庫的邏輯工作單位,事務中包含的各操作要麼都做,要麼都不做。

2、一致性:事務執行的結果必須是使資料庫從一個一致性狀態變到另一個一致性狀態。因此當資料庫只包含成功事務提交的結果時,就說資料庫處於一致性狀態。如果資料庫系統運行中發生故障,有些事務尚未完成就被迫中斷,這些未完成事務對資料庫所做的修改有一部分已寫入物理資料庫,這時資料庫就處於一種不正確的狀態,或者說是不一致的狀態。

3、隔離性:一個事務的執行不能其它事務幹擾。即一個事務內部的操作及使用的數據對其它並發事務是隔離的,並發執行的各個事務之間不能互相干擾。

4、持久性:也稱永久性,指一個事務一旦提交,它對資料庫中的數據的改變就應該是永久性的。接下來的其它操作或故障不應該對其執行結果有任何影響。

2、事務的隔離級別有幾種,最常用的隔離級別是哪兩種?

並發過程中會出現的問題:

丟失更新:是不可重複讀的特殊情況。如果兩個事物都讀取同一行,然後兩個都進行寫操作,並提交,第一個事物所做的改變就會丟失。髒讀:一個事務讀取到另一個事務未提交的更新數據。幻讀也叫虛讀:一個事務執行兩次查詢,第二次結果集包含第一次中沒有或某些行已經被刪除的數據,造成兩次結果不一致,只是另一個事務在這兩次查詢中間插入或刪除了數據造成的。不可重複讀:一個事務兩次讀取同一行的數據,結果得到不同狀態的結果,中間正好另一個事務更新了該數據,兩次結果相異,不可被信任。事務的隔離級別有4種:

1、未提交讀(Read uncommitted)

定義:就是一個事務讀取到其他事務未提交的數據,是級別最低的隔離機制。缺點:會產生髒讀、不可重複讀、幻讀。2、提交讀(Read committed)

定義:就是一個事務讀取到其他事務提交後的數據。Oracle默認隔離級別。缺點:會產生不可重複讀、幻讀。3、可重複讀(Repeatable read)

定義:就是一個事務對同一份數據讀取到的相同,不在乎其他事務對數據的修改。MySQL默認的隔離級別。缺點:會產生幻讀。4、串行化(Serializable)

定義:事務串行化執行,隔離級別最高,犧牲了系統的並發性。缺點:可以解決並發事務的所有問題。但是效率地下,消耗資料庫性能,一般不使用。

緩存

3、分布式緩存的典型應用場景?

頁面緩存,用來緩存Web頁面的內容片段,包括HTML、CSS 和圖片等,多應用於社交網站等。應用對象緩存,緩存系統作為ORM框架的二級緩存對外提供服務,目的是減輕資料庫的負載壓力,加速應用訪問。狀態緩存,緩存包括Session會話狀態及應用橫向擴展時的狀態數據等,這類數據一般是難以恢復的,對可用性要求較高,多應用於高可用集群。並行處理,通常涉及大量中間計算結果需要共享。事件處理,分布式緩存提供了針對事件流的連續查詢(continuous query)處理技術,滿足實時性需求。極限事務處理,分布式緩存為事務型應用提供高吞吐率、低延時的解決方案,支持高並發事務請求處理,多應用於鐵路、金融服務和電信等領域。資料庫

4、MongoDB與Mysql的區別?

兩種資料庫的區別:

傳統的關係型資料庫,數據是以表單為媒介進行存儲的。相比較Mysql,Mongodb以一種直觀文檔的方式來完成數據的存儲。Mongodb的鮮明特徵:

自帶GirdFS的分布式文件系統,這也為Mongodb的部署提供了很大便利。Mongodb內自建了對map-reduce運算框架的支持,雖然這種支持從功能上看還算是比較簡單的,相當於MySQL裡GroupBy功能的擴展版,不過也為數據的統計帶來了方便。Mongodb在啟動後將資料庫中得數據以文件映射的方式加載到內存中,如果內存資源相當豐富的話,這將極大的提高資料庫的查詢速度。Mongodb的優勢:

Mongodb適合那些對資料庫具體格式不明確或者資料庫數據格式經常變化的需求模型,而且對開發者十分友好。Mongodb官方就自帶一個分布式文件系統,Mongodb官方就自帶一個分布式文件系統,可以很方便的部署到伺服器機群上。Mongodb的缺陷:

事務關係支持薄弱。這也是所有NoSQL資料庫共同的缺陷,不過NoSQL並不是為了事務關係而設計的,具體應用還是很需求。穩定性有些欠缺方便開發者的同時,對運維人員提出了更高的要求。Mongodb的應用場景:

表結構不明確且數據不斷變大:MongoDB是非結構化文檔資料庫,擴展欄位很容易且不會影響原有數據。內容管理或者博客平臺等,例如圈子系統,存儲用戶評論之類的。更高的寫入負載:MongoDB側重高數據寫入的性能,而非事務安全,適合業務系統中有大量「低價值」數據的場景。本身存的就是json格式數據。例如做日誌系統。數據量很大或者將來會變得很大:Mysql單表數據量達到5-10G時會出現明細的性能降級,需要做數據的水平和垂直拆分、庫的拆分完成擴展,MongoDB內建了sharding、很多數據分片的特性,容易水平擴展,比較好的適應大數據量增長的需求。高可用性:自帶高可用,自動主從切換(副本集):不適用的場景:

MongoDB不支持事務操作,需要用到事務的應用建議不用MongoDB。MongoDB目前不支持join操作,需要複雜查詢的應用也不建議使用MongoDB。在帶「_id」插入數據的時候,MongoDB的插入效率其實並不高。如果想充分利用MongoDB性能的話,推薦採取不帶「_id」的插入方式,然後對相關欄位作索引來查詢。關係型資料庫和非關係型資料庫的應用場景對比:

關係型資料庫適合存儲結構化數據,如用戶的帳號、地址:

這些數據通常需要做結構化查詢,比如join,這時候,關係型資料庫就要勝出一籌。這些數據的規模、增長的速度通常是可以預期的。事務性、一致性。NoSQL適合存儲非結構化數據,如文章、評論:

這些數據通常用於模糊處理,如全文搜索、機器學習。這些數據是海量的,而且增長的速度是難以預期的。根據數據的特點,NoSQL資料庫通常具有無限(至少接近)伸縮性。按key獲取數據效率很高,但是對join或其他結構化查詢的支持就比較差。5、Mysql索引相關問題。

1)什麼是索引?

索引其實是一種數據結構,能夠幫助我們快速的檢索資料庫中的數據。

2)索引具體採用的哪種數據結構呢?

常見的MySQL主要有兩種結構:Hash索引和B+ Tree索引,通常使用的是InnoDB引擎,默認的是B+樹。

3)InnoDb內存使用機制?

Innodb體系結構如圖所示:

Innodb關於查詢效率有影響的兩個比較重要的參數分別是innodb_buffer_pool_size,innodb_read_ahead_threshold:

innodb_buffer_pool_size指的是Innodb緩衝池的大小,該參數的大小可通過命令指定innodb_buffer_pool_size 20G。緩衝池使用改進的LRU算法進行管理,維護一個LRU列表、一個FREE列表,FREE列表存放空閒頁,資料庫啟動時LRU列表是空的,當需要從緩衝池分頁時,首先從FREE列表查找空閒頁,有則放入LRU列表,否則LRU執行淘汰,淘汰尾部的頁分配給新頁。innodb_read_ahead_threshold相對應的是數據預加載機制,innodb_read_ahead_threshold 30表示的是如果一個extent中的被順序讀取的page超過或者等於該參數變量的,Innodb將會異步的將下一個extent讀取到buffer pool中,比如該參數的值為30,那麼當該extent中有30個pages被sequentially的讀取,則會觸發innodb linear預讀,將下一個extent讀到內存中;在沒有該變量之前,當訪問到extent的最後一個page的時候,Innodb會決定是否將下一個extent放入到buffer pool中;可以在Mysql服務端通過show innodb status中的Pages read ahead和evicted without access兩個值來觀察預讀的情況:Innodb_buffer_pool_read_ahead:表示通過預讀請求到buffer pool的pages;Innodb_buffer_pool_read_ahead_evicted:表示由於請求到buffer pool中沒有被訪問,而驅逐出內存的頁數。可以看出來,Mysql的緩衝池機制是能充分利用內存且有預加載機制,在某些條件下目標數據完全在內存中,也能夠具備非常好的查詢性能。

4)B+ Tree索引和Hash索引區別?

哈希索引適合等值查詢,但是無法進行範圍查詢。哈希索引沒辦法利用索引完成排序。哈希索引不支持多列聯合索引的最左匹配規則。如果有大量重複鍵值的情況下,哈希索引的效率會很低,因為存在哈希碰撞問題。5)B+ Tree的葉子節點都可以存哪些東西嗎?

InnoDB的B+ Tree可能存儲的是整行數據,也有可能是主鍵的值。

6)這兩者有什麼區別嗎?

在 InnoDB 裡,索引B+ Tree的葉子節點存儲了整行數據的是主鍵索引,也被稱之為聚簇索引。而索引B+ Tree的葉子節點存儲了主鍵的值的是非主鍵索引,也被稱之為非聚簇索引。

7)聚簇索引和非聚簇索引,在查詢數據的時候有區別嗎?

聚簇索引查詢會更快,因為主鍵索引樹的葉子節點直接就是我們要查詢的整行數據了。而非主鍵索引的葉子節點是主鍵的值,查到主鍵的值以後,還需要再通過主鍵的值再進行一次查詢。

8)主鍵索引查詢只會查一次,而非主鍵索引需要回表查詢多次(這個過程叫做回表)。是所有情況都是這樣的嗎?非主鍵索引一定會查詢多次嗎?

覆蓋索引(covering index)指一個查詢語句的執行只用從索引中就能夠取得,不必從數據表中讀取。也可以稱之為實現了索引覆蓋。當一條查詢語句符合覆蓋索引條件時,MySQL只需要通過索引就可以返回查詢所需要的數據,這樣避免了查到索引後再返回表操作,減少I/O提高效率。

如,表covering_index_sample中有一個普通索引 idx_key1_key2(key1,key2)。當我們通過SQL語句:select key2 from covering_index_sample where key1 = 'keytest';的時候,就可以通過覆蓋索引查詢,無需回表。

9)在創建索引的時候都會考慮哪些因素呢?

一般對於查詢概率比較高,經常作為where條件的欄位設置索引。

10)在創建聯合索引的時候,需要做聯合索引多個欄位之間順序,這是如何選擇的呢?

在創建多列索引時,我們根據業務需求,where子句中使用最頻繁的一列放在最左邊,因為MySQL索引查詢會遵循最左前綴匹配的原則,即最左優先,在檢索數據時從聯合索引的最左邊開始匹配。

所以當我們創建一個聯合索引的時候,如(key1,key2,key3),相當於創建了(key1)、(key1,key2)和(key1,key2,key3)三個索引,這就是最左匹配原則。

11)你知道在MySQL 5.6中,對索引做了哪些優化嗎?

索引條件下推:「索引條件下推」,稱為 Index Condition Pushdown (ICP),這是MySQL提供的用某一個索引對一個特定的表從表中獲取元組」,注意我們這裡特意強調了「一個」,這是因為這樣的索引優化不是用於多表連接而是用於單表掃描,確切地說,是單表利用索引進行掃描以獲取數據的一種方式。例如有索引(key1,key2),SQL語句中where key1 = 'XXX' and key2 like '%XXX%':如果沒有使用索引下推技術,MySQL會通過key1 = 'XXX'從存儲引擎返回對應的數據至MySQL服務端,服務端再基於key2 like 判斷是否符合條件。如果使用了索引下推技術,MySQL首先返回key1='XXX'的索引,再根據key2 like 判斷索引是否符合條件,如果符合則通過索引定位數據,如果不符合則直接reject掉。有了索引下推優化,可以在有like條件查詢的情況下,減少回表次數。12)如何知道索引是否生效?

explain顯示了MySQL如何使用索引來處理select語句以及連接表。可以幫助選擇更好的索引和寫出更優化的查詢語句。使用方法,在select語句前加上explain就可以了。

13)那什麼情況下會發生明明創建了索引,但是執行的時候並沒有通過索引呢?

在一條單表查詢語句真正執行之前,MySQL的查詢優化器會找出執行該語句所有可能使用的方案,對比之後找出成本最低的方案。這個成本最低的方案就是所謂的執行計劃。優化過程大致如下:

根據搜索條件,找出所有可能使用的索引。計算全表掃描的代價。計算使用不同索引執行查詢的代價。對比各種執行方案的代價,找出成本最低的那一個。14)為什麼索引結構默認使用B+Tree,而不是Hash,二叉樹,紅黑樹?

B+tree是一種多路平衡查詢樹,節點是天然有序的,非葉子節點包含多個元素,不保存數據,只用來索引,葉子節點包含完整數據和帶有指向下一個節點的指針,形成一個有序鍊表,有助於範圍和順序查找。因為非葉子節點不保存數據,所以同樣大小的磁碟頁可以容納更多的元素,同樣能數據量的情況下,B+tree相比B-tree高度更低,因此查詢時IO會更少。B-tree不管葉子節點還是非葉子節點,都會保存數據,這樣導致在非葉子節點中能保存的指針數量變少(有些資料也稱為扇出),指針少的情況下要保存大量數據,只能增加樹的高度,導致IO操作變多,查詢性能變低;Hash索引底層是基於哈希表,就是以key-value存儲數據的結構,多個數據在存儲關係上是沒有任何順序關係的。只適合等值查詢,不適合範圍查詢,而且也無法利用索引完成排序,不支持聯合索引的最左匹配原則,如果有大量重複鍵值的情況下,哈希索引效率會很低,因為存在哈希碰撞。二叉樹:樹的高度不均勻,不能自平衡,查找效率跟數據有關(樹的高度),並且IO代價高。紅黑樹:樹的高度隨著數據量增加而增加,IO代價高。6、如何優化MySQL?

MySQL優化大致可以分為三部分:索引的優化、SQL語句優化和表的優化

索引優化可以遵循以下幾個原則:

聯合索引最左前綴匹配原則儘量把欄位長度小的列放在聯合索引的最左側(因為欄位越小,一頁存儲的數據量越大,IO性能也就越好)order by 有多個列排序的,應該建立聯合索引對於頻繁的查詢優先考慮使用覆蓋索引前導模糊查詢不會使用索引,比如說Like '%aaa%'這種負向條件不會使用索引,如!=,<>,not like,not in,not exists索引應該建立在區分度比較高的欄位上 一般區分度在80%以上的時候就可以建立索引,區分度可以使用 count(distinct(列名))/count(*)對於where子句中經常使用的列,最好設置索引SQL語句優化,可以通過explain查看SQL的執行計劃,優化語句原則可以有:

在where和order by涉及的列上建立合適的索引,避免全表掃描任何查詢都不要使用select * ,而是用具體的欄位列表代替多表連接時,儘量小表驅動大表,即小表join大表用exists代替in儘量避免在where字句中對欄位進行函數操作資料庫表優化

表欄位儘可能用not null欄位長度固定表查詢會更快將資料庫大表按照時間或者一些標誌拆分成小表水平拆分:將記錄散列到不同的表中,每次從分表查詢垂直拆分:將表中的大欄位單獨拆分到另一張表,形成一對一的關係7、為什麼任何查詢都不要使用SELECT *?

多出一些不用的列,這些列可能正好不在索引的範圍之內(索引的好處不多說)select * 杜絕了索引覆蓋的可能性,而索引覆蓋又是速度極快,效率極高,業界極為推薦的查詢方式。(索引覆蓋)資料庫需要知道 * 等於什麼 = 查數據字典會增大開銷(記錄資料庫和應用程式元數據的目錄)。不需要的欄位會增加數據傳輸的時間,即使 mysql 伺服器和客戶端是在同一臺機器上,使用的協議還是 tcp,通信也是需要額外的時間。大欄位,例如很長的 varchar,blob,text。準確來說,長度超過 728 字節的時候,會把超出的數據放到另外一個地方,因此讀取這條記錄會增加一次 io 操作。(mysql innodb)影響資料庫自動重寫優化SQL(類似 Java 中編譯 class 時的編譯器自動優化) 。(Oracle)select * 資料庫需要解析更多的 對象,欄位,權限,屬性相關,在 SQL 語句複雜,硬解析較多的情況下,會對資料庫造成沉重的負擔。額外的 io,內存和 cpu 的消耗,因為多取了不必要的列。用 SELECT * 需謹慎,因為一旦列的個數或順序更改,就有可能程序執行失敗。多線程

Java實現多線程有幾種方式?

有三種方式:

繼承Thread類,並重寫run方法。實現Runnable接口,並重寫run方法。實現Callable接口,並重寫run方法,並使用FutureTask包裝器。線程的生命周期

1、新建狀態(New):新創建了一個線程對象。

2、就緒狀態(Runnable):線程對象創建後,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。

3、運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。

4、阻塞狀態(Blocked):阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:

等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。(wait會釋放持有的鎖)同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池中。其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。(注意,sleep是不會釋放持有的鎖)5、死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命周期。

start()方法和run()方法的區別?

start()方法會使得該線程開始執行,java虛擬機會去調用該線程的run()方法。通過調用Thread類的 start()方法來啟動一個線程,這時此線程處於就緒(可運行)狀態,並沒有運行,一旦得到cpu時間片,就開始執行run()方法,這裡方法 run()稱為線程體,它包含了要執行的這個線程的內容,run方法運行結束,此線程隨即終止。run()方法只是類的一個普通方法而已,如果直接調用run方法,程序中依然只有主線程這一個線程,其程序執行路徑還是只有一條,還是要順序執行,還是要等待run方法體執行完畢後才可繼續執行下面的代碼,這樣就沒有達到多線程的目的。Runnable接口和Callable接口的區別?

Runnable接口中的run()方法的返回值是void,它做的事情只是純粹地去執行run()方法中的代碼而已。Callable接口中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合可以用來獲取異步執行的結果。這其實是很有用的一個特性,因為多線程相比單線程更難、更複雜的一個重要原因就是因為多線程充滿著未知性,某條線程是否執行了?某條線程執行了多久?某條線程執行的時候我們期望的數據是否已經賦值完畢?無法得知,我們能做的只是等待這條多線程的任務執行完畢而已。而Callable + Future/FutureTask卻可以獲取多線程運行的結果,可以在等待時間太長沒獲取到需要的數據的情況下取消該線程的任務,真的是非常有用。volatile關鍵字

volatile基本介紹:volatile可以看成是synchronized的一種輕量級的實現,但volatile並不能完全代替synchronized,volatile有synchronized可見性的特性,但沒有synchronized原子性的特性。可見性即用volatile關鍵字修飾的成員變量表明該變量不存在工作線程的副本,線程每次直接都從主內存中讀取,每次讀取的都是最新的值,這也就保證了變量對其他線程的可見性。另外,使用volatile還能確保變量不能被重排序,保證了有序性。

當一個變量定義為volatile之後,它將具備兩種特性:

①保證此變量對所有線程的可見性:當一條線程修改了這個變量的值,新值對於其他線程可以說是可以立即得知的。Java內存模型規定了所有的變量都存儲在主內存,每條線程還有自己的工作內存,線程的工作內存保存了該線程使用到的變量在主內存的副本拷貝,線程對變量的所有操作都必須在工作內存中進行,而不能直接讀取主內存中的變量。②禁止指令重排序優化: volatile boolean isOK = false; //假設以下代碼在線程A執行 A.init(); isOK=true; //假設以下代碼在線程B執行 while(!isOK){ sleep(); } B.init();

A線程在初始化的時候,B線程處於睡眠狀態,等待A線程完成初始化的時候才能夠進行自己的初始化。這裡的先後關係依賴於isOK這個變量。如果沒有volatile修飾isOK這個變量,那麼isOK的賦值就可能出現在A.init()之前(指令重排序,Java虛擬機的一種優化措施),此時A沒有初始化,而B的初始化就破壞了它們之前形成的那種依賴關係,可能就會出錯。

volatile使用場景:

如果正確使用volatile的話,必須依賴下以下種條件:

對變量的寫操作不依賴當前變量的值。該變量沒有包含在其他變量的不變式中。在以下兩種情況下都必須使用volatile:

狀態的改變。讀多寫少的情況。什麼是線程安全?

如果你的代碼在多線程下執行和在單線程下執行永遠都能獲得一樣的結果,那麼你的代碼就是線程安全的。

線程安全的級別:

1)不可變:像String、Integer、Long這些,都是final類型的類,任何一個線程都改變不了它們的值,要改變除非新創建一個,因此這些不可變對象不需要任何同步手段就可以直接在多線程環境下使用。2)絕對線程安全:不管運行時環境如何,調用者都不需要額外的同步措施。要做到這一點通常需要付出許多額外的代價,Java中標註自己是線程安全的類,實際上絕大多數都不是線程安全的,不過絕對線程安全的類,Java中也有,比方說CopyOnWriteArrayList、CopyOnWriteArraySet。3)相對線程安全:相對線程安全也就是我們通常意義上所說的線程安全,像Vector這種,add、remove方法都是原子操作,不會被打斷,但也僅限於此,如果有個線程在遍歷某個Vector、有個線程同時在add這個Vector,99%的情況下都會出現ConcurrentModificationException,也就是fail-fast機制。4)線程非安全:ArrayList、LinkedList、HashMap等都是線程非安全的類。sleep方法和wait方法有什麼區別?

原理不同:sleep()方法是Thread類的靜態方法,是線程用來控制自身流程的,它會使此線程暫停執行一段時間,而把執行機會讓給其他線程,等到計時時間一到,此線程會自動甦醒。而wait()方法是Object類的方法,用於線程間的通信,這個方法會使當前擁有該對象鎖的進程等待,直到其他線程用調用notify()或notifyAll()時才甦醒過來,開發人員也可以給它指定一個時間使其自動醒來。對鎖的處理機制不同:由於sleep()方法的主要作用是讓線程暫停一段時間,時間一到則自動恢復,不涉及線程間的通信,因此調用sleep()方法並不會釋放鎖。而wait()方法則不同,當調用wait()方法後,線程會釋放掉它所佔用的鎖,從而使線程所在對象中的其他synchronized數據可被別的線程使用。使用區域不同:wait()方法必須放在同步控制方法或者同步語句塊中使用,而sleep方法則可以放在任何地方使用。sleep()方法必須捕獲異常,而wait()、notify()、notifyAll()不需要捕獲異常。在sleep的過程中,有可能被其他對象調用它的interrupt(),產生InterruptedException異常。由於sleep不會釋放鎖標誌,容易導致死鎖問題的發生,一般情況下,不推薦使用sleep()方法,而推薦使用wait()方法。寫一個會導致死鎖的程序。

public class MyThread{ private static Object lock1 = new Object(); private static Object lock2 = new Object(); public static void main(String[] args) { new Thread(()->{ synchronized (lock1){ System.out.println("thread1 get lock1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2){ System.out.println("thread1 get lock2"); } System.out.println("thread1 end"); } }).start(); new Thread(()->{ synchronized (lock2){ System.out.println("thread2 get lock2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1){ System.out.println("thread2 get lock1"); } System.out.println("thread2 end"); } }).start(); } }

類加載過程

1、類加載過程:加載->連結(驗證+準備+解析)->初始化(使用前的準備)->使用->卸載

具體過程如下:

1)加載:首先通過一個類的全限定名來獲取此類的二進位字節流;其次將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構;最後在java堆中生成一個代表這個類的Class對象,作為方法區這些數據的訪問入口。總的來說就是查找並加載類的二進位數據。

2)連結:

驗證:確保被加載類的正確性。

準備:為類的靜態變量分配內存,並將其初始化為默認值。

解析:把類中的符號引用轉換為直接引用。

符號引用即用字符串符號的形式來表示引用,其實被引用的類、方法或者變量還沒有被加載到內存中。直接引用則是有具體引用地址的指針,被引用的類、方法或者變量已經被加載到內存中。

直接引用可以是:

直接指向目標的指針。(個人理解為:指向對象,類變量和類方法的指針)相對偏移量。(指向實例的變量,方法的指針)一個間接定位到對象的句柄。為什麼要使用符號引用?

符號引用要轉換成直接引用才有效,這也說明直接引用的效率要比符號引用高。那為什麼要用符號引用呢?這是因為類加載之前,javac會將原始碼編譯成.class文件,這個時候javac是不知道被編譯的類中所引用的類、方法或者變量他們的引用地址在哪裡,所以只能用符號引用來表示,當然,符號引用是要遵循java虛擬機規範的。

還有一種情況需要用符號引用,就例如前文舉得變量的符號引用的例子,是為了邏輯清晰和代碼的可讀性。

3)為類的靜態變量賦予正確的初始值。

2、類的初始化

1)類什麼時候才被初始化:

創建類的實例,也就是new一個對象。訪問某個類或接口的靜態變量,或者對該靜態變量賦值。調用類的靜態方法。反射(Class.forName(「com.lyj.load」))。初始化一個類的子類(會首先初始化子類的父類)。JVM啟動時標明的啟動類,即文件名和類名相同的那個類。2)類的初始化順序

如果這個類還沒有被加載和連結,那先進行加載和連結假如這個類存在直接父類,並且這個類還沒有被初始化(注意:在一個類加載器中,類只能初始化一次),那就初始化直接的父類(不適用於接口)加入類中存在初始化語句(如static變量和static塊),那就依次執行這些初始化語句。總的來說,初始化順序依次是:(靜態變量、靜態初始化塊)–>(變量、初始化塊)–> 構造器;如果有父類,則順序是:父類的靜態變量 –> 父類的靜態代碼塊 –> 子類的靜態變量 –> 子類的靜態代碼塊 –> 父類的非靜態變量 –> 父類的非靜態代碼塊 –> 父類的構造方法 –> 子類的非靜態變量 –> 子類的非靜態代碼塊 –> 子類的構造方法。3、類的加載

類的加載指的是將類的.class文件中的二進位數據讀入到內存中,將其放在運行時數據區的方法區內,然後在堆區創建一個這個類的java.lang.Class對象,用來封裝類在方法區類的對象。如:

類的加載的最終產品是位於堆區中的Class對象。Class對象封裝了類在方法區內的數據結構,並且向Java程式設計師提供了訪問方法區內的數據結構的接口。加載類的方式有以下幾種:

從本地系統直接加載。通過網絡下載.class文件。從zip,jar等歸檔文件中加載.class文件。從專有資料庫中提取.class文件。將Java源文件動態編譯為.class文件(伺服器)。

4、加載器

JVM的類加載是通過ClassLoader及其子類來完成的,類的層次關係和加載順序可以由下圖來描述:

加載器介紹:

1)BootstrapClassLoader(啟動類加載器):

負責加載JAVA_HOME中jre/lib/rt.jar裡所有的class,加載System.getProperty(「sun.boot.class.path」)所指定的路徑或jar。

2)ExtensionClassLoader(標準擴展類加載器):

負責加載java平臺中擴展功能的一些jar包,包括JAVAHOME中jre/lib/rt.jar裡所有的class,加載System.getProperty(「sun.boot.class.path」)所指定的路徑或jar。2)ExtensionClassLoader(標準擴展類加載器):負責加載java平臺中擴展功能的一些jar包,包括JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包。載System.getProperty(「java.ext.dirs」)所指定的路徑或jar。

3)AppClassLoader(系統類加載器):

負責加載classpath中指定的jar包及目錄中class。

4)CustomClassLoader(自定義加載器):

屬於應用程式根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現。

類加載器的順序

加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。在加載類時,每個類加載器會將加載任務上交給其父,如果其父找不到,再由自己去加載。Bootstrap Loader(啟動類加載器)是最頂級的類加載器了,其父加載器為null。5、類加載器之雙親委派模型

所謂的雙親委派模型指除了啟動類加載器以外,其餘的加載器都有自己的父類加載器,而在工作的時候,如果一個類加載器收到加載請求,他不會馬上加載類,而是將這個請求向上傳遞給他的父加載器,看父加載器能不能加載這個類,加載的原則就是優先父加載器加載,如果父加載器加載不了,自己才能加載。因為有了雙親委派模型的存在,類似Object類重複多次的問題就不會存在了,因為經過層層傳遞,加載請求最終都會被Bootstrap ClassLoader所響應。加載的Object對象也會只有一個。並且面對同一JVM進程多版本共存的問題,只要自定義一個不向上傳遞加載請求的加載器就好啦。

垃圾回收機制

Java內存區域劃分

我們先來看看Java的內存區域劃分情況,如下圖所示:

私有內存區的區域名和相應的特性如下表所示:

虛擬機棧中的局部變量表裡面存放了三個信息:

各種基本數據類型(boolean、byte、char、short、int、float、long、double)。對象引用(reference)。returnAddress地址。這個returnAddress和程序計數器有什麼區別?前者是指示JVM的指令執行到了哪一行,後者是指你的代碼執行到哪一行。

共享內存區(接下來主要講jdk1.7)的區域名和相應的特性如下表所示:

哪些內存需要回收?

私有內存區伴隨著線程的產生而產生,一旦線程中止,私有內存區也會自動消除,因此我們在本文中討論的內存回收主要是針對共享內存區。

Java堆

新生代GC(Minor GC):指發生在新生代的垃圾收集動作,因為Java對象大都具備朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度也比較快。

老年代GC(Major GC/Full GC):指發生在老年代的GC,出現了Major GC,經常會伴隨至少一次的Minor GC (但非絕對,在Parallel Scavenge收集器的收集策略裡就有直接進行Major GC的策略選擇過程)。Major GC的速度一般會比Minor GC慢10倍以上。

新生代:剛剛新建的對象在Eden中,經歷一次Minor GC, Eden中的存活對象就被移動到第一塊survivor space S0,Eden被清空;等Eden區再滿了,就再觸發一次Minor GC, Eden和S0中的存活對象會被複製送入第二塊survivor space S1。S0和Eden被清空,然後下一輪S0與S1交換角色,如此循環往復。如果對象的複製次數達到16次,該對象就被送到老年代中。

為什麼新生代內存需要有兩個Sruvivor區:

先不去想為什麼有兩個Survivor區,第一個問題是,設置Survivor區的意義在哪裡?

如果沒有Survivor,Eden區每進行一次Minor GC,存活的對象就會被送到老年代。老年代很快被填滿,觸發Major GC(因為Major GC一般伴隨著Minor GC,也可以看做觸發了Full GC)。老年代的內存空間遠大於新生代,進行一次Full GC消耗的時間比Minor GC長得多。你也許會問,執行時間長有什麼壞處?頻發的Full GC消耗的時間是非常可觀的,這一點會影響大型程序的執行和響應速度,更不要說某些連接會因為超時發生連接錯誤了。那我們來想想在沒有Survivor的情況下,有沒有什麼解決辦法,可以避免上述情況:

顯而易見,沒有Survivor的話,上述兩種解決方案都不能從根本上解決問題。我們可以得到第一條結論:Survivor的存在意義,就是減少被送到老年代的對象,進而減少Full GC的發生,Survivor的預篩選保證,只有經歷16次Minor GC還能在新生代中存活的對象,才會被送到老年代。

設置兩個Survivor區最大的好處就是解決了碎片化,下面我們來分析一下。為什麼一個Survivor區不行?

第一部分中,我們知道了必須設置Survivor區。假設現在只有一個survivor區,我們來模擬一下流程:

剛剛新建的對象在Eden中,一旦Eden滿了,觸發一次Minor GC,Eden中的存活對象就會被移動到Survivor區。這樣繼續循環下去,下一次Eden滿了的時候,問題來了,此時進行Minor GC,Eden和Survivor各有一些存活對象,如果此時把Eden區的存活對象硬放到Survivor區,很明顯這兩部分對象所佔有的內存是不連續的,也就導致了內存碎片化。

那麼,順理成章的,應該建立兩塊Survivor區,剛剛新建的對象在Eden中,經歷一次Minor GC,Eden中的存活對象就會被移動到第一塊survivor space S0,Eden被清空;等Eden區再滿了,就再觸發一次Minor GC,Eden和S0中的存活對象又會被複製送入第二塊survivor space S1(這個過程非常重要,因為這種複製算法保證了S1中來自S0和Eden兩部分的存活對象佔用連續的內存空間,避免了碎片化的發生)。S0和Eden被清空,然後下一輪S0與S1交換角色,如此循環往復。如果對象的複製次數達到16次,該對象就會被送到老年代中。

老年代:如果某個對象經歷了幾次垃圾回收之後還存活,就會被存放到老年代中。老年代的空間一般比新生代大。

這個流程如下圖所示:

什麼時候回收?

Java並沒有給我們提供明確的代碼來標註一塊內存並將其回收。或許你會說,我們可以將相關對象設為null或者用System.gc()。然而,後者將會嚴重影響代碼的性能,因為每一次顯示調用system.gc()都會停止所有響應,去檢查內存中是否有可回收的對象,這會對程序的正常運行造成極大威脅。

另外,調用該方法並不能保障JVM立即進行垃圾回收,僅僅是通知JVM要進行垃圾回收了,具體回收與否完全由JVM決定。

生存還是死亡

可達性算法:這個算法的基本思路是通過一系列的稱為「GC Roots」的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。

二次標記:在可達性分析算法中被判斷是對象不可達時不一定會被垃圾回收機制回收,因為要真正宣告一個對象的死亡,必須經歷兩次標記的過程。

如果發現對象不可達時,將會進行第一次標記,此時如果該對象調用了finalize()方法,那麼這個對象會被放置在一個叫F-Queue的隊列之中,如果在此隊列中該對象沒有成功拯救自己(拯救自己的方法是該對象有沒有被重新引用),

那麼GC就會對F-Queue隊列中的對象進行小規模的第二次標記,一旦被第二次標記的對象,將會被移除隊列並等待被GC回收,所以finalize()方法是對象逃脫死亡命運的最後一次機會。

在Java語言中,可作為GC Roots的對象包括下面幾種:

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

引用計數法(Reference Counting):

給對象添加一個引用計數器,每過一個引用計數器值就+1,少一個引用就-1。當它的引用變為0時,該對象就不能再被使用。它的實現簡單,但是不能解決互相循環引用的問題。

優點:

及時回收無效內存,實時性高。垃圾回收過程中無需掛起。沒有全局掃描,性能高。缺點:

對象創建時需要更新引用計數器,耗費一部分時間。浪費CPU資源,計數器統計需要實時進行。無法解決循環引用問題,即使對象無效仍不會被回收。標記-清除(Mark-Sweep)算法:

分為兩個階段:首先標記出所有需要回收的對象,在標記完成後統一回收所有被標記的對象(後續的垃圾回收算法都是基於此算法進行改進的)。

缺點:效率問題,標記和清除兩個過程的效率都不高;空間問題,會產生很多碎片。

複製算法:

將可用內存按容量劃分為大小相等的兩塊,每次只用其中一塊。當這一塊用完了,就將還存活的對象複製到另外一塊上面,然後把原始空間全部回收。高效、簡單。

缺點:將內存縮小為原來的一半。

標記-整理(Mark-Compat)算法

標記過程與標記-清除算法過程一樣,但後面不是簡單的清除,而是讓所有存活的對象都向一端移動,然後直接清除掉端邊界以外的內存。

分代收集(Generational Collection)算法

新生代中,每次垃圾收集時都有大批對象死去,只有少量存活,就選用複製算法,只需要付出少量存活對象的複製成本就可以完成收集。

老年代中,其存活率較高、沒有額外空間對它進行分配擔保,就應該使用「標記-整理」或「標記-清除」算法進行回收。

增量回收GC和並行回收GC這裡就不做具體介紹了,有興趣的朋友可以自行了解一下。

垃圾收集器

Serial收集器:單線程收集器,表示在它進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集結束。"Stop The World"。

ParNew收集器:實際就是Serial收集器的多線程版本。

並發(Parallel):指多條垃圾收集線程並行工作,但此時用戶線程仍然處於等待狀態。並行(Concurrent):指用戶線程與垃圾收集線程同時執行,用戶程序在繼續運行,而垃圾收集程序運行於另一個CPU上。Parallel Scavenge收集器:該收集器比較關注吞吐量(Throughout)(CPU用於用戶代碼的時間與CPU總消耗時間的比值),保證吞吐量在一個可控的範圍內。

CMS(Concurrent Mark Sweep)收集器:CMS收集器是一種以獲取最短回收停頓時間為目標的垃圾收集器,是基於「標記——清除」算法實現的。

其回收過程主要分為四個步驟:

初始標記:標記一下GC Roots能直接關聯到的對象,速度很快。並發標記:進行GC Roots Tracing的過程,也就是標記不可達的對象,相對耗時。重新標記:修正並發標記期間因用戶程序繼續運作導致的標記變動,速度比較快。並發清除:對標記的對象進行統一回收處理,比較耗時。由於初始標記和重新標記速度比較快,其它工作線程停頓的時間幾乎可以忽略不計,所以CMS的內存回收過程是與用戶線程一起並發執行的。初始標記和重新標記兩個步驟需要Stop the world;並發標記和並發清除兩個步驟可與用戶線程並發執行。「Stop the world」意思是垃圾收集器在進行垃圾回收時,會暫停其它所有工作線程,直到垃圾收集結束為止。

CMS的缺點:

對CPU資源非常敏感;也就是說當CMS開啟垃圾收集線程進行垃圾回收時,會佔用部分用戶線程,如果在CPU資源緊張的情況下,會導致用戶程序的工作效率下降。無法處理浮動垃圾導致又一次FULL GC的產生;由於CMS並發回收垃圾時用戶線程同時也在運行,伴隨用戶線程的運行自然會有新的垃圾產生,這部分垃圾出現在標記過程之後,CMS無法在當次收集過程中進行回收,只能在下一次GC時在進行清除。所以在CMS運行期間要確保內存中有足夠的預留空間用來存放用戶線程的產生的浮動垃圾,不允許像其它收集器一樣等到老年代區完全填滿了之後再進行收集;那麼當內存預留的空間不足時就會產生又一次的FULL GC來釋放內存空間,由於是通過Serial Old收集器進行老年代的垃圾收集,所以導致停頓的時間變長了(系統有一個閾值來觸發CMS收集器的啟動,這個閾值不允許太高,太高反而導致性能降低)。標記——清除算法會產生內存碎片;如果產生過多的內存碎片時,當系統虛擬機想要再分配大對象時,會找不到一塊足夠大的連續內存空間進行存儲,不得不又一次觸發FULL GC。G1(Garbage First)收集器:G1收集器是一款成熟的商用的垃圾收集器,是基於「標記——整理」算法實現的。

其回收過程主要分為四個步驟:

初始標記:標記一下GC Roots能直接關聯到的對象,速度很快。並發標記:進行GC Roots Tracing的過程,也就是標記不可達的對象,相對耗時。最終標記:修正並發標記期間因用戶程序繼續運作導致的標記變動,速度比較快。篩選回收:首先對各個Region的回收價值和成本進行排序,根據用戶所期望的GC停頓時間來制定回收計劃。G1收集器的特點:

並發與並行:機型垃圾收集時可以與用戶線程並發運行。分代收集:能根據對象的存活時間採取不同的收集算法進行垃圾回收。不會產生內存碎片:基於標記——整理算法和複製算法保證不會產生內存空間碎片。可預測的停頓:G1除了追求低停頓時間外,還能建立可預測的停頓時間模型,便於用戶的實時監控。CMS收集器與G1收集器的區別:

CMS採用標記——清除算法會產生空間碎片,G1採用標記——整理算法不會產生空間碎片。G1可以建立可預測的停頓時間模型,而CMS則不能。JDK 1.8 JVM的變化

1、為什麼取消方法區

它在啟動時固定大小,很難進行調優,並且FullGC時會移動類元信息。類及方法的信息等比較難確定大小,因此對永久代的大小指定比較困難。在某些場景下,如果動態加載類過多,容易造成Perm區的OOM。字符串存在方法區中,容易出現性能問題和內存溢出。永久代GC垃圾回收效率偏低。2、JDK 1.8裡Perm區中的所有內容中字符串常量移至堆內存,其他內容如類元信息、欄位、靜態屬性、方法、常量等都移動到元空間內。

3、元空間

元空間(MetaSpace)不在堆內存上,而是直接佔用的本地內存。因此元空間的大小僅受本地內存限制

也可通過參數來設定元空間的大小:

-XX:MetaSpaceSize 初始元空間大小-XX:MaxMetaSpaceSize 最大元空間大小除了上面兩個指定大小的選項以外,還有兩個與 GC 相關的屬性:

-XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少為分配空間所導致的垃圾收集。

-XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少為釋放空間所導致的垃圾收集。

元空間的特點:

每個加載器有專門的存儲空間。不會單獨回收某個類。元空間裡的對象的位置是固定的。如果發現某個加載器不再存貨了,會把相關的空間整個回收。性能優化:

減少new對象。每次new對象之後,都要開闢新的內存空間。這些對象不被引用之後,還要回收掉。因此,如果最大限度地合理重用對象,或者使用基本數據類型替代對象,都有助於節省內存。多使用局部變量,減少使用靜態變量。局部變量被創建在棧中,存取速度快。靜態變量則是存儲在堆內存中。避免使用finalize,該方法會給GC增添很大的負擔。如果是單線程,儘量使用非多線程安全的,因為線程安全來自於同步機制,同步機制會降低性能。例如,單線程程序,能使用HashMap,就不要使用HashTabl。同理,儘量減少使用synchronized。用移位符號替代乘除號。比如:a*8應該寫作a<<3。對於經常反覆使用的對象使用緩存。儘量使用基本類型而不是包裝類型,儘量使用一維數組而不是二維數組。儘量使用final修飾符,final表示不可修改,訪問效率高。單線程下(或者是針對於局部變量),字符串儘量使用StringBuilder,比StringBuffer要快。儘量使用StringBuffer來連接字符串。這裡需要注意的是,StringBuffer的默認緩存容量是16個字符,如果超過16,append方法調用私有的expandCapacity()方法,來保證足夠的緩存容量。因此,如果可以預設StringBuffer的容量,避免append再去擴展容量。java自動裝箱拆箱總結

當基本類型包裝類與基本類型值進行==運算時,包裝類會自動拆箱。即比較的是基本類型值。

具體實現上,是調用了Integer.intValue()方法實現拆箱。

int a = 1;Integer b = 1;Integer c = new Integer(1);System.out.println(a == b); //trueSystem.out.println(a == c); //trueSystem.out.println(c == b); //falseInteger a = 1;會調用這個 Integer a = Integer.valueOf(1);Integer已經默認創建了數值【-128到127】的Integer常量池Integer a = -128;Integer b = -128;System.out.println(a == b); //trueInteger a = 128;Integer b = 128;System.out.println(a == b); //falseJava的數學計算是在內存棧裡操作的c1 + c2 會進行拆箱,比較還是基本類型int a = 0;Integer b1 = 1000;Integer c1 = new Integer(1000);Integer b2 = 0;Integer c2 = new Integer(0);System.out.println(b1 == b1 + b2); //trueSystem.out.println(c1 == c1 + c2); //trueSystem.out.println(b1 == b1 + a); //trueSystem.out.println(c1 == c1 + a); //true

↓↓↓Java高級架構師課程免費體驗課

相關焦點

  • 2019 最新 200 道 Java 面試題
    ,我做了大量的「功課」,首先我研究了幾乎所有大廠的面試題,還和負責招聘工作的幾個朋友,詳細的探討了 Java 面試所要涉及的知識點,於是就有了今天大家看到的這 200 多道面試題。為什麼要公開這些面試題?原因一:身邊從事 Java 開發的人員越來越多,我的表弟表妹們,朋友的表弟表妹們,朋友的朋友的表弟表妹們,每次問我要相同的面試複習材料,已經讓我疲於應付,索性整理出來,直接發連結給他們。
  • Java 最常見的 200+ 面試題:面試必備
    慢慢的我產生了一個想法,要不要把我整理的這 200 多道面試題分享出來,來幫助更多需要的人。說實話剛開始的時候還是比較猶豫的,首先我會覺得這麼做會不會有點幫人「作弊」的嫌疑,最後我想通了,這是一件值得去做的事兒。
  • Java 最常見面試題
    30.哪些集合類是線程安全的?31.迭代器 Iterator 是什麼?32.Iterator 怎麼使用?有什麼特點?33.Iterator 和 ListIterator 有什麼區別?34.怎麼確保一個集合不能被修改?三、多線程35.並行和並發有什麼區別?36.線程和進程的區別?
  • java線程前傳——jvm內存結構、內存模型和cpu結構
    一、當代計算機體系結構1、當代計算CPU體系大體上如下每個CPU有一個共享緩存,名為三級緩存L3每個CPU有多個核心組成每個核心有兩級緩存,分別叫一級緩存L1,二級緩存L2一級緩存分兩種,分別叫數據緩存(L1
  • 四面阿里斬獲offer定級P7,2020最新最全阿里巴巴68道高級面試題
    二面(線程、資料庫、緩存、協議等):講一下項目線程池由哪些組件組成?有哪些線程池,分別怎麼使用?拒絕策略有哪些?什麼時候多線程會發生死鎖,寫一個例子?Redis的數據結構是什麼? 線程模型說一下?講講Redis的數據淘汰機制?說說Redis的數據一致性問題?
  • JAVA面試題:你在項目中使用多線程的場景?
    背景在JAVA面試時,很多面試官都會問多線程在項目中的實際應用場景,這個時候我們通常不知道如何回答。因為我們大多數程式設計師通常都是和業務代碼打交道,需要用到多線程的地方我們容器和框架一般都替我們處理好了,所以我們很少有機會接觸到多線程編程。
  • Java 線程面試題 Top 50
    僅僅知道線程的基本概念是遠遠不夠的, 你必須知道如何處理死鎖,競態條件,內存衝突和線程安全等並發問題。掌握了這些技巧,你就可以輕鬆應對多線程和並發麵試了。許多Java程式設計師在面試前才會去看面試題,這很正常。因為收集面試題和練習很花時間,所以我從許多面試者那裡收集了Java多線程和並發相關的50個熱門問題。我只收集了比較新的面試題且沒有提供全部答案。
  • 面試總結——Java高級工程師
    2、深入了解並熟記部分java基礎知識原因:大部分公司無論面試初級還是高級,無論是筆試還是面試,都會問到一系列基礎題,這些基礎題大概涵括jvm、字符串、集合、線程等等,如果說每一個讓你死記硬背你肯定記不住,那麼就是理解中記憶了,拿jvm來說 ,如果讓你談談你對jvm的理解, 那麼你首先得知道JVM有哪些結構組成,每個結構用來做什麼的,然後考慮一個Java對象從創建到回收,
  • 2020最新Java後端面試題(帶答案),重點都給畫出來了!你不看?
    前言金九銀十,金三銀四確實是跳槽旺季,但是拋除這個之外,每天都有人在面試,有些人迷茫,有些人躊躇滿志,可能人們總在想,如果能知道面試內容就好了,其實我們可以從網上找到80%的面試題,自己去提前準備。有準備的面試總是讓人信心滿滿的。
  • Java面試題-多線程篇十三
    線程是作業系統能夠進行運算調度的最小單位,它被包含在進程之中,是進程中的實際運作單位。程式設計師可以通過它進行多處理器編程,你可以使用多線程對運算密集型任務提速。比如,如果一個線程完成一個任務要100毫秒,那麼用十個線程完成改任務只需10毫秒。122,線程和進程有什麼區別?
  • 經典java面試題23道
    (1)ArrayList底層是用數組實現的,所以查找快,增刪慢,ArrayList是線程不安全的(2)Vector由於是線程安全的,所以他的效率要比ArrayList底的多6,Super關鍵字的使用?Query是查詢的意思,在資料庫中查詢一個文件或記錄, 個人理解:List和Iterate都可以訪問資料庫,只不過iterator如果要查找的數據緩存中有就直接從緩存中拿,而不去在查找資料庫了。
  • Java面試題參考答案I
    簡單談談JVM內存模型,以及volatile關鍵字:雖然java程序所有的運行都是在虛擬機中,涉及到的內存等信息都是虛擬機的一部分,但實際也是物理機的,只不過是虛擬機作為最外層的容器統一做了處理。虛擬機的內存模型,以及多線程的場景下與物理機的情況是很相似的,可以類比參考。
  • 面試官經常喜歡問的Mybatis經典面試題,值得好好收藏哦!
    今天給大家分享一些面試官喜歡提問的Mybatis面試題,好了,廢話不多說,直接上乾貨吧!一、請說說在Mybatis 中#和$有什麼區別?#相當於對數據 加上 雙引號,$相當於直接顯示數據1.資料庫連接創建、釋放頻繁造成系統資源浪費從而影響系統性能,如果使用資料庫連接池可解決此問題。解決:在 SqlMapConfig.xml 中配置數據連接池,使用連接池管理資料庫連接。2. Sql 語句寫在代碼中造成代碼不易維護,實際應用 sql 變化的可能較大,sql 變動需要改變 java 代碼。
  • Java面試題全集 第二彈(史上最強)
    面試題:2005年摩託羅拉的面試中曾經問過這麼一個問題「If a process reports a stack overflow run-time error, what’s the most possible cause?」
  • 工作五年,一年內我靠這系列java面試寶典從13K到大廠30K
    你在多線程環境中遇到的常見的問題是什麼?你是怎麼解決它的?5. ……JVM阿里面試必問JVM!MySQL面試題請解釋關係型資料庫概念及主要特點?Redis面試題redis 和 memcached 什麼區別?為什麼高並發下有時單線程的 redis 比多線程的memcached 效率要高?
  • 13道 常見的JVM 面試題
    對象都在堆裡創建,為了提升效率線程會從堆中弄一個緩存到自己的棧,如果多個線程使用該變量就可能引發問題,這時volatile 變量就可以發揮作用了,它要求線程從主存中讀取變量的值。5、JVM中一次完整的GC流程是怎樣的GC是垃圾收集的意思,Java語言沒有提供釋放已分配內存的顯示操作方法。開發者不用擔心內存管理,因為垃圾收集器會自動進行管理。
  • 知名網際網路公司校招 Java 開發崗面試知識點解析
    限於篇幅有限,更多高並發編程中的問題,請各位參閱我的博客:Java 多線程編程實戰指南(核心篇)讀書筆記(一)Java 多線程編程實戰指南(核心篇)讀書筆記(二)Java 多線程編程實戰指南(核心篇)讀書筆記(三)Java 多線程編程實戰指南(核心篇)讀書筆記(四)Java 多線程編程實戰指南(核心篇)讀書筆記(五)
  • 2019年java常見面試題
    本人今年2月份來到上海來尋求工作,已經面試了10多家了,在這裡分享一下我的心得和常問到的面試題。>一),根據10多家面試的經驗,問的最多的是sql語句方面的問題,我詳細的列出一下1,sql語句怎麼去重?
  • 網友面試N多家公司,孟哥幫你總結面試題,再也不怕面試了
    先要提前一天做一套線上筆試題,不管有沒有通過,第二天都是要去面試的。主要是項目相關的,業務上的東西問得多,具體的問題有:1,代碼優化2,mysql優化3,jvm內存模型4,spring cloud用過哪些組件 講講熔斷機制5,為什麼離開上家公司不是很難但是沒有offer,心態崩了。
  • 面試前必看Java線程面試題
    下面是Java線程相關的熱門面試題,你可以用它來好好準備面試。1.面向對象的特徵有哪些方面?答:面向對象的特徵主要有以下幾個方面:- 抽象:抽象是將一類對象的共同特徵總結出來構造類的過程,包括數據抽象和行為抽象兩方面。