JAVA 實習面試題大全必看

JAVA 實習面試題大全必看

JavaSE 88

基礎語法 9

Q1:簡單說說Java有哪些數據類型
答:①分爲基本數據類型和引用數據類型。②基本數據類型包括:數值型(byte、short、int、long、float、double),字符型(char)以及布爾型(boolean)。除了基本類型外,其他數據類型都屬於引用類型,包括類、接口、數組等。

Q2:float number=3.4;有沒有問題?爲什麼?
答:有問題,因爲3.4 是雙精度數,將雙精度型(double)賦值給浮點型(float)屬於向下轉型,可能會造成精度損失,所以必須進行強制類型轉換,正確的寫法是float number =(float)3.4;/ float number =3.4F;

Q3:字符串拼接的方式以及效率?
答:①使用+直接拼接,String 是final對象,不會被修改,每次使用 +進行拼接都會創建新的對象,而不是改變原來的對象,效率低,是線程安全的。②使用StringBuffer可變字符串,效率較高,是線程安全的(StringBuffer的方法使用了synchronized關鍵字進行修飾)。③使用StringBuilder可變字符串,效率最高,但是線程不安全。

Q4:簡述final,finally和finalize區別
答:①final可以修飾類,方法和變量,被final修飾的類不可繼承,被final修飾的方法不可重寫,被final修飾的變量引用不可更改,引用的內容可以更改。②finally用於try-catch代碼塊中,無論是否發生異常最後都將執行,作用是釋放資源。③finalize是Object類的方法,在對象被垃圾回收之前將調用一次,一般用於資源的釋放。

Q5:==和equals有什麼區別?equals和hashCode有什麼聯繫?
答:①如果是引用類型,==比較的是兩個對象的引用是否完全相同,如果是基本類型,比較的是兩個基本類型的數值是否相同。②如果沒有重寫的話,equals默認按照==進行比較,如果重寫了equals()方法,則按照對應的比較規則比較。③兩個對象如果相等,那麼它們的hashCode值必須相等,但兩個對象的hashCode值相等時,它們不一定相同。

Q6:Array和ArrayList的區別?
答:①Array長度在定義之後就不運行改變了,而ArrayList是長度可變的,可以自動擴容。②Array只能存儲相同類型的數據,ArrayList可以存儲不同類型的數據。③ArrayList提供了更多操作數據的方法。

Q7:&和&&的區別?
答:①&具有按位與和邏輯與兩個功能。②&&作爲邏輯與具有短路的特點,當前面的條件表達式爲false時就不會進行後面條件表達式的判斷,可以用來避免空指針異常。

Q8:簡述JDK8的新特性
答:①接口中可以添加default修飾的非抽象方法,可以有方法體和內容。②可以使用lambda表達式,減少代碼冗餘。③函數式接口,使用@FunctionalInterface註解標明,該接口有且僅有一個抽象方法。④方法引用,可以直接引用已有Java類或對象的方法或構造器,進一步簡化lambda表達式。⑤stream流,用於解決已有集合/數組類庫的弊端,簡化其操作,有foreach遍歷、filter過濾、map映射、concat合併等功能。⑥增加日期相關的API。

Q9:Stream流了解嗎?
答:①Stream流是JDK1.8的新特性,用於解決已有集合/數組類庫的弊端,簡化集合/數組的操作。②stream流的獲取:集合:直接調用stream()方法獲取;數組:使用靜態方法Stream.of()/Arrays.stream()獲取。③常用方法:forEach() 遍歷;count() 統計個數;filter() 按條件過濾;limit() 取前面n個元素;skip() 跳過前面n個元素;map() 映射加工;concat() 合併stream流。④終結方法:foreach/count 調用終結方法後流不能繼續使用;非終結方法:每次調用完返回一個新的stream對象,可以繼續使用,支持鏈式編程。⑤收集stream流:把流轉爲Set集合 collect(Collections.toSet());把流轉爲List集合 collect(Collections.toList());把流轉爲Collection集合 collect(Collections.toCollection());把流轉爲數組 toArray()


面向對象 10

Q1:簡述面向對象的特性
答:①封裝:建議成員變量私有,然後提供公有的getter/setter方法來獲取值/賦值,封裝的核心思想是合理隱藏,合理暴露,可以提高安全性,實現代碼的組件化。②繼承:一種子類到父類的關係,是「is a」關係,可以提高代碼的複用性,相同代碼可寫到父類,子類的功能更加強大,不僅得到了父類的功能,還有自己的功能。③多態:同一個類型的對象執行相同的行爲,在不同的狀態下表現出不同的特徵。多態可以降低類之間的耦合度,右邊對象可以實現組件化切換,業務功能隨之改變,便於擴展和維護。

Q2:類和對象有什麼區別?
答:類是一個抽象的概念,是具有相同特徵的事物的描述,是對象的模板。對象是一個個具體的存在,是類的實例。

Q3:列舉Object類的方法
答:①equals(Object obj):判斷其他對象是否與當前對象相等。②toString():打印當前對象的字符串表示。③wait():導致當前線程等待,等待其他線程喚醒,會釋放鎖。④notify()/notifyAll():隨機喚醒一個/全部線程。⑤hashCode():返回當前對象的hashCode值。⑥finalize():當垃圾回收器要回收對象前調用。⑦clone():創建並返回對象的一個副本。

Q4:方法重載和方法重寫的區別?
答:①方法重載是同一個類中具有不同參數列表的同名方法(無關返回值類型),方法重寫是子類中具有和父類相同參數列表的同名方法,會覆蓋父類原有的方法。②重載的返回值類型和權限修飾符,異常拋出類型沒有要求,重寫方法的返回值類型小於等於父類被重寫方法的返回值類型,修飾符權限大於等於父類被重寫方法權限修飾符,拋出的異常類型小於等於父類被重寫方法拋出的異常類型。

Q5:接口和抽象類有什麼區別?
答:①接口中只能定義public staic final修飾的常量,抽象類中可以定義普通變量。②接口和抽象類都不能實例化,但接口沒有構造器,抽象類有構造器。③接口可以多實現,抽象類只能單繼承。④接口在JDK1.8之前只能定義public abstract修飾的方法,JDK1.8開始可以定義默認方法和靜態方法,JDK1.9開始可以定義私有方法,抽象類中的方法沒有限制。

Q6:什麼時候應該使用接口,什麼時候應該使用抽象類?
答:①如果知道某個類應該成爲基類,那麼第一選擇應該是讓它成爲一個接口,只有在必須要有方法定義和成員變量的時候,才應該選擇抽象類。②在接口和抽象類的選擇上,必須遵守這樣一個原則:行爲模型應該總是通過接口而不是抽象類定義。通過抽象類建立行爲模型會出現的問題:如果有一個抽象類Moblie,有兩個繼承它的類Mobile1和Moblie2,分別有自己的功能1和功能2,如果出現一個既有功能1又有功能2的新產品需求,由於Java不允許多繼承就出現了問題,而如果是接口的話只需要同時實現兩個接口即可。

Q7:內部類有什麼作用?有哪些分類?
答:①內部類有更好的封裝性,有更多的權限修飾符,封裝性可以得到更多的控制。②靜態內部類:由static修飾,屬於類本身,只加載一次。類可以定義的成分靜態內部類都可以定義,可以訪問外部類的靜態變量和方法,通過new 外部類.靜態內部類構造器來創建對象。③成員內部類:屬於外部類的每個對象,隨對象一起加載。不可以定義靜態成員和方法,可以訪問外部類的所有內容,通過new 外部類構造器.new 成員內部類構造器來創建對象。④局部內部類:定義在方法、構造器、代碼塊、循環中。只能定義實例成員變量和實例方法,作用範圍僅在局部代碼塊中。⑤匿名內部類:沒有名字的局部內部類,可以簡化代碼,匿名內部類會立即創建一個匿名內部類的對象返回,對象類型相當於當前new的類的子類類型。
在這裏插入圖片描述

Q8:泛型和泛型擦除是什麼?
答:①泛型的本質是參數化類型,泛型提供了編譯時類型的安全檢測機制,該機制允許程序在編譯時檢測非法的類型。②在編譯階段採用泛型時加上的類型參數,會被編譯器在編譯時去掉,這個過程就被稱爲類型擦除,因此泛型主要用於編譯階段,在編譯後生成的Java字節代碼文件中不包含泛型中的類型信息。

Q9:泛型標記的規範瞭解嗎?
答:①E:值Element,在集合中使用,表示在集合中存放的元素。②T:指Type,表示Java類,包括基本的類以及自定義類。③K:指Key,表示鍵,例如Map集合中的Key。④V:指Value,表示值,例如Map集合中的Value。⑤N:指Number,表示數值類型。⑥:表示不確定的Java類型。

Q10:泛型限定是什麼?
答:①類型通配符使用?表示所有具體的參數類型,在使用泛型的時候,如果希望將類的繼承關係加入泛型應用中就需要對泛型做限定,具體的泛型限定有對泛型上限的限定以及對泛型下限的限定。②對泛型上限的限定使用<? extends T>,它表示該通配符所代表的類型是T類的子類型或T接口的子接口。③對泛型下限的限定使用<? super T>,它表示該通配符所代表的類型是T類的父類型或T接口的父接口。


異常 2

Q1:異常有哪些分類?出現的原因是什麼?
答:①Throwable是所有錯誤和異常的父類,Throwable分爲Error和Exception。②Error指Java程序運行錯誤,出現Error通常是因爲系統的內部錯誤或資源耗盡,Error不能在運行過程中被動態處理,如果程序運行中出現Error,系統只能記錄錯誤的原因和安全終止。③Exception指Java程序運行異常,即運行中發生了不期望的情況,分爲RuntimeException和CheckedException。RuntimeException指在Java虛擬機正常運行期間拋出的異常,可以被捕獲並處理,例如空指針異常,數組越界等。CheckedException指編譯階段強制要求捕獲並處理的異常,例如IO異常,SQL異常等。

Q2:有哪些異常處理方式?
答:①拋出異常:遇到異常不進行具體處理,而是將異常拋出給調用者,由調用者根據情況處理。拋出異常有2種形式,一種是throws,作用在方法上,一種是throw,作用在方法內。②使用try/catch進行異常的捕獲處理,try中發生的異常會被catch代碼塊捕獲,根據情況進行處理,如果有finally代碼塊無論是否發生異常都會執行,一般用於釋放資源,JDK1.7開始可以將資源定義在try代碼塊中自動釋放減少代碼。


集合 10

Q1:簡述一下集合主要有哪些類和接口,各自有什麼特點
答:①主要有兩個接口Collection和Map,其中Collection又包括List、Set和Queue。②List是有序的,主要包括ArrayList,LinkedList和Vector,ArrayList底層通過數組實現,線程不安全,Vector是線程安全的ArrayList,但效率較低,LinkedList底層通過雙向鏈表實現,與ArrayList相比增刪快查詢慢。③Set是唯一且無序的,主要包括HashSet,LinkedHashSet和TreeSet。HashSet底層其實就是HashMap,利用了key來保證元素的唯一性。LinkedHashSet可以按照key的操作順序排序,TreeSet支持按照默認或指定的排序規則排序。④Queue是隊列結構,主要有ArrayBlockingQueue基於數組的阻塞隊列、LinkedBlockingQueue基於鏈表的阻塞隊列等。⑤Map以key-value鍵值對的形式存儲元素,主要包括HashMap、LinkedHashMap和TreeMap。HashMap底層通過數組+鏈表/紅黑樹實現,LinkedHashMap可以按照key的操作順序對集合排序,TreeMap可以按照默認或指定的排序規則對集合排序。

Q2:HashMap是線程安全的嗎?
答:①HashMap是線程不安全的,可以使用ConcurrentHashMap保證線程安全。②ConcurrentHashMap基於減小鎖粒度的思想,通過使用分段鎖來實現線程安全,內部細分爲很多Segment數據段,默認情況下爲16個,對每個Segment的數據都單獨進行加鎖操作,Segment的個數爲鎖的併發度。ConcurrentHashMap是由Segment數組和HashEntry數組組成的,Segment繼承了可重入鎖,HashEntry用來存儲鍵值對數據。③Segment的結構和HashMap類似,是數組和鏈表結構,每個Segment裏面都包含一個HashEntry數組,每個HashEntry都是一個鏈表結構的數據要對其進行i修改必須先獲得對應的Segment鎖。④多線程下只要加入的數據hashCode映射的數據段不一樣就可以做到並行的線程安全。

Q3:List、Set、Map有什麼區別?
答:①List是有序、可重複、有索引的集合,繼承了Collection集合全部功能 除了Collection的三種遍歷方式外,可用索引遍歷。②Set是無序,不可重複的集合,Set的實現類LinkedHashSet和TreeSet是有序的,LinkedHashSet可以按照元素插入的順序排序,也可以按照元素操作的時間排序,TreeSet可以按照默認的比較規則或者自定義的比較規則排序。③Map是無序、以key-value的鍵值對形式存儲元素的集合,鍵不可重複,值無要求,重複的鍵對應的值會覆蓋之前的值。

Q4:HashSet是如何去重的?
答:①對於基本類型的包裝類,可以直接按值進行比較。②對於引用數據類型,會先比較hashCode()返回值是否相同,如果不同則代表不是同一個對象,如果相同則繼續比較equals()方法返回值是否相同,都相同說明是同一個對象。③如果希望內容相同的對象就代表對象相同,那麼除了重寫equals()方法還要重寫hashCode()方法,因爲內容相同的對象hashCode()值不一定相同,因爲只有hashCode()和equals()都相同才說明是同一個對象。

Q5:HashMap和HashSet的底層是怎麼實現的?
答:①JDK1.8之前,HashMap的底層是數組加鏈表實現。數組中的每個元素都是一個單鏈表,鏈表中的每個元素都是Entry的實現類Node的一個實例,Node包括4個屬性:key、value、hash值和用於指向單鏈表下一個元素的next。②HashMap在查找數據時,根據hash值可以快速定位到數組的具體下標,然後對鏈表進行遍歷查找數據的時間複雜度爲O(n)。JDK1.8起對HashMap進行了優化,底層改爲數組+鏈表或紅黑樹,當鏈表中的元素超過8個之後,HashMap會將鏈表結構轉換未紅黑樹以提高查詢效率,時間複雜度爲O(logn)。②HashSet的底層是基於HashMap實現的,HashSet中的元素只是存放在了底層HashMap的key上, 而value使用一個static final的Object對象標識。因此HashSet 的實現比較簡單,相關操作基本上都是直接調用底層HashMap的相關方法來完成的。

Q6:Collection和Collections有什麼區別?
答:①Collection是一個集合接口,它包括List有序集合、Set無序集合、Queue隊列等。②Collections則是Collection的一個工具類,爲Collection類型的對象提供了很多方便的方法,例如addAll可以直接對Collection集合批量添加元素,shuffle可以隨機打亂List集合的元素順序,sort可以對List集合進行默認或按比較器進行排序。

Q7:迭代器是什麼?
答:①迭代器實現了Iterator接口,是用於遍歷Collection集合元素的一個指針。②主要有三個方法:通過iterator()獲得集合的迭代器;通過hasNext()判斷集合當中是否還有元素,如果有返回true,沒有則返回false,初始時迭代器位於第一個元素之前;通過next()獲取集合的下一個元素,並向後移動一個元素的單位。

Q8:在使用foreach循環遍歷集合元素時能否添加或刪除元素?
答:使用foreach循環遍歷元素集合時不能修改或刪除元素,通過java -c查看字節碼可以發現foreach循環實際上是用Iterator迭代器實現的,如果進行添加或刪除元素會拋出ConcurrentModificationException異常,因爲添加或刪除元素會改變modCount的值,modCount是集合類的一個成員變量,代表集合的修改次數,當modCount的值和預期的exceptedModCount值不一致時就會拋出ConcurrentModificationException異常。

Q9:Queue接口中的add()/offer()、remove()/poll()、element()/peek()方法有什麼區別?
答:①add()和offer()都是向隊列尾部插入一個元素,區別是當超出隊列界限時,add方法會拋出異常,而offer()會返回false。②remove()和poll()都是從隊列頭部移除一個元素並返回,區別是隊列爲空時remove()方法會拋出異常,poll()方法則是返回null值。③element()和 peek() 都是用於查詢隊列頭部的元素,區別時隊列爲空時, element() 拋出一個異常,而 peek() 返回 null。

Q10:有哪些線程安全的集合類?
答:①Vector,是線程安全的ArrayList,底層用數組實現,通過synchronized修飾方法保證線程安全。②HashTable,是線程安全的HashMap,繼承自Dictionary,通過synchronized修飾方法保證線程安全,性能較差。③ConcurentHashMap,線程安全的HashMap,通過分段鎖實現線程安全,性能較好。


多線程 34

Q1:創建線程有哪幾種實現方式?分別有什麼優缺點?
答:①繼承Thread類,重寫run()方法即可。優點是編碼簡單,缺點是不能繼承其他類,功能單一。②實現Runnable接口,重寫run()方法,並將該實現類作爲參數傳入Thread構造器。優點是可以繼承其他類,避免了單繼承的侷限性;適合多個相同程序代碼的線程共享一個資源(同一個線程任務對象可被包裝成多個線程對象),實現解耦操作,代碼和線程獨立。缺點是實現相對複雜。③實現Callable接口,重寫call()方法,幷包裝成FutureTask對象,再作爲參數傳入Thread構造器。優點是相比方式二可以獲取返回值,缺點是實現複雜。④可以通過線程池創建。

Q2:線程有哪些狀態?
答:①New:用new操作創建一個新線程,此時程序還未開始運行線程中的代碼。②Runnable:調用start()方法後進入可運行狀態。③Blocked:阻塞狀態,內部鎖(不是juc中的鎖)獲取失敗時進入阻塞狀態。④Waiting:等待其他線程喚醒時進入等待狀態。⑤Timed Waiting:計時等待,帶超時參數的方法,例如sleep(long time)。⑥Terminated:終止狀態,線程正常運行完畢或被未捕獲異常終止。

Q3:什麼是線程安全問題,如何解決?
答:當多個線程對同一個共享變量進行操作時可能會產生的問題。解決方法:①使用內部鎖synchronized,可以使用同步代碼塊,如果是實例方法可用this作爲鎖對象,如果是靜態方法,可以用類.class作爲鎖,或者使用同步方法底層和同步代碼塊一樣,如果是實例方法默認用this作爲鎖,如果是靜態方法默認使用類.class。②使用java.util.concurrent包中的鎖,例如ReentrantLock。

Q4:多線程不可見問題的原因和解決方式?
答:①不可見的原因是每個線程有自己的工作內存,線程都是從主內存拷貝共享變量的副本值。每個線程都是在自己的工作內存操作共享變量的。②解決方式:加鎖:獲得鎖後線程會清空工作內存,從主內存拷貝共享變量最新的值成爲副本,修改後刷新回主內存,再釋放鎖;使用volatile關鍵字:被volatile修飾的變量會通知其他線程之前讀取到的值已失效,線程會加載最新值到自己的工作內存。

Q5:說一說volatile關鍵字的作用
答:①保證被修飾的變量對所有線程可見,在一個線程修改了變量的值後,新的值對於其他線程是可以立即獲取的。②禁止指令重排序,被修飾的變量不會被緩存在寄存器中或者對其他處理器不可見的地方,因此在讀取volatile修飾的變量時總是會返回最新寫入的值。③不會執行加鎖操作,不會導致線程阻塞,主要適用於一個變量被多個線程共享,多個線程均可對這個變量執行賦值或讀取的操作。④volatile可以嚴格保證變量的單次讀寫操作的原子性,但並不能保證像i++這種操作的原子性,因爲i++在本質上是讀、寫兩次操作。

Q6:說一說synchronized關鍵字的作用
答:①用於爲Java對象、方法、代碼塊提供線程安全的操作,屬於排它的悲觀鎖,也屬於可重入鎖。②被synchronized修飾的方法和代碼塊在同一時刻只能有一個線程訪問,其他線程只有等待當前線程釋放鎖資源後才能訪問。③Java中的每個對象都有一個monitor監視器對象,加鎖就是在競爭monitor,對代碼塊加鎖是通過在前後分別加上monitorenter和monitorexit指令實現的,對方是否加鎖是通過一個標記位來判斷的。

Q7:synchronized的內部都包括哪些區域?
答:synchronized內部包括6個不同的區域,每個區域的數據都代表鎖的不同狀態。①ContentionList:鎖競爭隊列,所有請求鎖的線程都被放在競爭隊列中。②EntryList:競爭候選列表,在鎖競爭隊列中有資格成爲候選者來競爭鎖資源的線程被移動到候選列表中。③WaitSet:等待集合,調用wait方法後阻塞的線程將被放在WaitSet。④OnDeck:競爭候選者,在同一時刻最多隻有一個線程在競爭鎖資源,該線程的狀態被稱爲OnDeck。⑤Owner:競爭到鎖資源的線程狀態。⑥!Owner:釋放鎖後的狀態。

Q8:簡述synchronized的實現原理
答:①收到新的鎖請求時首先自旋,如果通過自旋也沒有獲取鎖資源,被放入ContentionList(該做法對於已經進入隊列的線程是不公平的,體現了synchronized的不公平性)。②爲了防止ContentionList尾部的元素被大量線程進行CAS訪問影響性能,Owner線程會在是釋放鎖時將ContentionList的部分線程移動到EntryList並指定某個線程(一般是最先進入的)爲OnDeck線程。Owner並沒有將鎖直接傳遞給OnDeck線程而是把鎖競爭的權利交給他,該行爲叫做競爭切換,犧牲了公平性但提高了性能。③獲取到鎖的OnDeck線程會變爲Owner線程,未獲取到的仍停留在EntryList中。④Owner線程在被wait阻塞後會進入WaitSet,直到某個時刻被喚醒再次進入EntryList。⑤ContentionList、EntryList、WaitSet中的線程均爲阻塞狀態。⑥當Owner線程執行完畢後會釋放鎖資源並變爲!Owner狀態。

Q9:JDK對synchronized做了哪些優化?
答:JDK1.6中引入了適應自旋、鎖消除、鎖粗化、輕量級鎖以及偏向鎖等以提高鎖的效率。鎖可以從偏向鎖升級到輕量級鎖,再升級到重量級鎖,這種過程叫做鎖膨脹。JDK1.6中默認開啓了偏向鎖和輕量級鎖,可以通過-XX:UseBiasedLocking禁用偏向鎖。

Q10:volatile和synchronized的區別?
答:①volatile只能修飾實例變量和類變量,而synchronized可以修飾方法以及代碼塊。②
volatile只能保證數據的可見性,但是不保證原子性,synchronized是一種排它機制,可以保證原子性。只有在特殊情況下才適合取代synchronized:對變量的寫操作不依賴於當前值(例如i++),或者是單純的變量賦值;該變量沒有被包含在具有其他變量的不等式中,不同的volatile變量不能互相依賴,只有在狀態真正獨立於程序內的其它內容時才能使用volatile。③volatile是一種輕量級的同步機制,在訪問volatile修飾的變量時並不會執行加鎖操作,線程不會阻塞,使用synchronized加鎖會阻塞線程。

Q11:講一講ReentrantLock
答:①ReentrantLock是Lock接口的實現類,是一個可重入式的獨佔鎖,通過AQS實現。②支持公平鎖與非公平鎖,還提供了可響應中斷鎖(線程在等待鎖的過程中可以根據需要取消對鎖的請求,通過interrupt方法中斷)、可輪詢鎖(通過tryLock獲取鎖,如果有可用鎖返回true否則立即返回false)、定時鎖(通過帶long時間參數的tryLock方法獲取鎖,如果在給定時間內獲取到可用鎖且當前線程未被中斷返回true,如果超過指定時間則返回false,如果獲取鎖時被終斷則拋出異常並清除已終止狀態)等避免死鎖的方法。③通過lock和unlock方法顯式地加鎖和釋放鎖。

Q12:synchronized和ReentrantLock有哪些區別?
答:①synchronized是隱式鎖,ReentrantLock是顯式鎖,使用時必須在finally代碼塊中進行釋放鎖的操作。②synchronized是非公平鎖,ReentrantLock可以實現公平鎖。③ReentrantLock可響應中斷,可輪迴,爲處理鎖提高了更多靈活性。④synchronized是一個關鍵字,是JVM級別,ReentrantLock是一個接口,是API級別。⑤synchronized採用悲觀併發策略,ReentrantLock採用的是樂觀併發策略,會先嚐試以CAS方式獲取鎖。

Q13:Lock接口有哪些方法?
答:①lock():給對象加鎖。②tryLock()/tryLock(long time,TimeUnit unit):嘗試給對象加鎖,成功返回true,可以無參也可以指定等待時間。③unlock():釋放鎖,鎖只能由持有者釋放否則拋出異常。④newCondition():創建條件對象,使用條件對象管理那些已經獲得鎖但不滿足有效條件的線程,調用await()方法把線程進入等待集,調用sign()/signAll()解除阻塞。⑤lockInterruptibly():如果當前線程未被中斷則獲取該鎖。

Q14:Java中的鎖有什麼作用?有哪些分類?
答:①Java中的鎖主要用於保障多併發情況下數據的一致性,線程必須先獲取鎖才能進行操作,可以保證數據的安全。②從樂觀和悲觀的角度可以分爲樂觀鎖和悲觀鎖。③從獲取資源的公平性可以分爲公平鎖和非公平鎖。④從是否共享資源的角度可以分爲共享鎖和排它鎖。⑤從鎖的狀態角度可分爲偏向鎖、輕量級鎖和重量級鎖。同時在JVM中還設計了自旋鎖以更快地使用CPU資源。

Q15:講一講樂觀鎖和悲觀鎖
答:①樂觀鎖採用樂觀的思想處理數據,在每次讀取數據時都認爲別人不會修改該數據,所以不會上鎖。但在更新時會判斷在此期間別人有沒有更新該數據,通常採用在寫時先讀出當前版本號然後加鎖的方法,具體過程爲:比較當前版本號與上一次的版本號,如果一致則更新,否則重複進行讀、比較、寫操作。Java中的樂觀鎖是基於CAS操作實現的,CAS是一種原子性操作,在對數據更新之前先比較當前值和傳入的值是否一樣,一樣則更新否則直接返回失敗狀態。②悲觀鎖採用悲觀的思想處理數據,每次讀取數據時都認爲別人會修改數據,所以每次都會上鎖,其他線程將被阻塞。Java中的悲觀鎖基於AQS實現,該框架下的鎖會先嚐試以CAS樂觀鎖去獲取鎖,如果獲取不到則會轉爲悲觀鎖。

Q16:講一講自旋鎖
答:①自旋鎖認爲如果持有鎖的線程能在很短的時間內釋放鎖資源,那麼那些等待競爭鎖的線程就不需要做內核態和用戶態之間的切換進入阻塞、掛起狀態,只需等待小段時間,在等待持有鎖的線程釋放鎖後即可立即獲取鎖,這樣就避免了用戶線程在內核態的切換上導致鎖時間消耗。②優點:減少CPU的上下文切換,對於佔用鎖時間非常短或鎖競爭不激烈的代碼塊來說性能很高。③缺點:在持有鎖的線程長時間佔用鎖或競爭過於激烈時,線程會長時間自旋浪費CPU資源,有複雜鎖依賴的情況不適合使用自旋鎖。

Q17:講一講公平鎖與非公平鎖
答:①公平鎖指在分配鎖前檢查是否有線程在排隊等待獲取該鎖,優先將鎖分配給排隊時間最長的線程。②非公平鎖指在分配鎖時不考慮線程排隊等待的情況,直接嘗試獲取鎖,獲取不到鎖就在排到隊尾等待。③因爲公平鎖需要在多核情況下維護一個鎖線程等待隊列,基於該隊列進行鎖的分配,因此效率比非公平鎖低很多。synchronized是非公平鎖,ReentrantLock默認的lock方法也是非公平鎖。

Q18:講一講讀寫鎖
答:①Lock接口提供的鎖是普通鎖,爲了提高性能Java提供了讀寫鎖,讀寫鎖分爲讀鎖和寫鎖,讀鎖之間不互斥,讀鎖與寫鎖,寫鎖之間都互斥。②如果系統要求共享數據可以同時支持很多線程併發讀,但不能支持很多線程併發寫,那麼讀鎖能大大提高效率。如果系統要求共享數據在同一時刻只能有一個線程在寫,且寫的過程中不能讀,則需要使用寫鎖。③提高juc的locks包中ReadWriteLock的實現類ReentrantReadWriteLock的readLock()和writeLock()來分別獲取讀鎖和寫鎖。

Q19:講一講共享鎖與排它鎖
答:①共享鎖:允許多個線程同時獲取該鎖,併發訪問共享資源,ReentrantReadWriteLock的讀鎖爲共享鎖的實現。②排它鎖:也叫互斥鎖 ,每次只允許有一個線程獨佔該鎖,ReentrantLock爲排它鎖的實現。③排它鎖是一種悲觀的加鎖策略,同一時刻只允許一個線程讀取鎖資源,限制了讀操作的併發性,因爲併發讀線程並不會影響數據的一致性,因此共享鎖採用了樂觀的加鎖策略,允許多個執行讀操作的線程同時訪問共享資源。

Q20:鎖有哪些狀態?
答:①無鎖,偏向鎖,輕量級鎖和重量級鎖。②重量級鎖是基於操作系統互斥量實現的,會導致進程在用戶態和內核態之間來回切換,開銷較大,synchronized內部基於監視器實現,監視器基於底層操作系統實現,因此屬於重量級鎖,運行效率不高。JDK1.6後爲了減少獲取鎖和釋放鎖帶來的性能消耗提高性能,引入了輕量級鎖和偏向鎖。③輕量級鎖是相對於重量級鎖而言的,核心設計實在沒有多線程競爭的前提下,減少重量級鎖的使用來提高性能。適用於線程交替執行同步代碼塊的情況,如果同一時刻有多線程訪問同一個鎖,會導致輕量級鎖膨脹成重量級鎖。④偏向鎖用於在某個線程獲取某個鎖後,消除這個線程鎖重入的開銷,看起來似乎是這個線程得到了鎖的偏袒。偏向鎖的主要目的是在同一個線程多次獲取某個所的情況下儘量減少輕量級鎖的執行路徑,因爲輕量級鎖需要多次CAS操作,而偏向鎖只需要切換ThreadID時執行一次CAS操作,提高效率。出現多線程競爭鎖時,JVM會自動撤銷偏向鎖。偏向鎖是進一步提高輕量級鎖性能的。⑤隨着鎖競爭越來越嚴重,鎖可能從偏向鎖升級到輕量級鎖再到重量級鎖,但在Java中只會單向升級不會降級。

Q21:如何進行鎖優化?
答:①減少鎖持有的時間:只在有線程安全要求的程序上加鎖來儘量減少同步代碼塊對鎖的持有時間。②減小鎖粒度:將單個耗時較多的鎖操作拆分爲多個耗時較少的鎖操作來增加鎖的並行度,減少同一個鎖上的競爭。在減少鎖的競爭後,偏向鎖、輕量級鎖的使用率纔會提高,例如ConcurrentHashMap中的分段鎖。③讀分離:指根據不同的應用場景將鎖的功能進行分離以應對不同的變化,最常見的鎖分離思想就是讀寫鎖,這樣既保證了線程安全又提高了性能。④鎖粗化:指爲了保障性能,會要求儘可能將鎖的操作細化以減少線程持有鎖的時間,但如果鎖分的太細反而會影響性能提升,這種情況下建議將關聯性強的鎖操作集中處理。⑤鎖消除:注意代碼規範,消除不必要的鎖來提高性能。

Q22:線程池是什麼?爲什麼需要線程池?
答:①在生產中爲每一個任務創建一個線程存在一些缺陷,如果無限制地大量創建線程會消耗很多資源,影響系統穩定性和性能,產生內存溢出等問題。②線程池是管理一組同構工作線程的資源池,線程池與工作隊列密切相關,工作隊列中保存了所有需要等待執行的任務。工作線程的任務很簡單,從工作隊列獲取任務,執行任務,返回線程池並等待下一次任務。③線程池通過重用現有的線程,可以在處理多個請求時分攤線程在創建和撤銷過程中的開銷,另一個好處是當請求到達時工作線程通常已經存在,不會出現等待線程而延遲的任務的執行,提高了響應性。通過調整線程池的大小,可以創建足夠多的線程保持處理器處於忙碌狀態,同時還可以防止線程過多導致內存資源耗盡。

Q23:創建線程池時,ThreadPoolExecutor構造器中都有哪些參數,有什麼含義?
答:①corePoolSize: 線程池核心大小,即在沒有任務執行時線程池的大小,並且只有在工作隊列滿了的情況下才會創建超出這個數量的線程。②maximumPoolSize: 線程池最大大小,表示可同時活動的線程數量的上限。③keepAliveTime:存活時間,如果某個線程的空閒時間超過了存活時間,那麼將被標記爲可回收的,並且當線程池的當前大小超過基本大小時,這個線程將被終止。④unit: 存活時間的單位,可選的參數爲TimeUnit枚舉中的幾個靜態變量: NANOSECONDSMICROSECONDSMILLISECONDSSECONDS。⑤workQueue: 線程池所使用的阻塞隊列。⑥thread factory:線程池使用的創建線程工廠方法,可省略,將使用默認工廠。⑦handler:所用的拒絕執行處理策略,可省略,將使用默認拒絕執行策略。

Q24:線程池的阻塞隊列有哪些選擇?
答:①ArrayBlockingQueue:基於數組的有界阻塞隊列。②LinkedBlockingQueue:基於鏈表的有界阻塞隊列。③PriorityBlockingQueue:支持優先級排序的無界阻塞隊列。④DelayedWorkQueue:基於優先級隊列的無界阻塞隊列。⑤SynchronousQueue:隊列內部僅允許容納一個元素,用於控制互斥的阻塞隊列。

Q25:線程池的拒絕執行策略有哪些選擇?
答:①AbortPolicy(): 線程池默認的拒絕策略,拋出RejectedExecutionException異常。②CallerRunsPolicy(): 重試添加當前的任務,他會自動重複調用execute()方法。③DiscardOldestPolicy(): 拋棄舊的任務,加入新的任務。④DiscardPolicy(): 直接拋棄當前的任務。

Q26:創建線程池的方法有哪些?
答:可以通過Executors的靜態工廠方法創建線程池,內部通過重載ThreadExecutorPool不同的構造器創建線程池。①newFixedThreadPool,創建一個固定長度的線程池,每當提交一個任務就創建一個線程,直到達到線程池的最大數量,這時線程池的規模將不再變化(如果某個線程由於發生了未預期的異常而結束,那麼線程池會補充一個新的線程)。將線程池的核心大小和最大大小都設置爲參數中指定的值,創建的線程不會超時,使用LinkedBlockingQueue。②newCachedThreadPool,創建一個可緩存的線程池,如果線程池的當前規模超過了處理器需求,那麼將回收空閒的線程,而當需求增加時,可以添加新的線程,線程池的規模不存在任何限制。將線程池的最大大小設置爲Integer.MAX_VALUE,而將核心大小設置爲0,並將超時設爲1分鐘,使用SynchronousQueue,這種方法創建出的線程池可被無限擴展,並當需求降低時自動收縮。③newSingleThreadExecutor,一個單線程的Executor,創建單個工作者線程來執行任務,如果這個線程異常結束,會創建另一個線程來代替。確保依照任務在隊列中的順序來串行執行。將核心線程和最大線程數都設置爲1,使用LinkedBlockingQueue。④newScheduledThreadPool,創建一個固定長度的線程池,而且以延遲或定時的方式來執行任務,類似於Timer,使用DelayedWorkQueue

Q27:線程池的工作原理?
答:①線程池剛創建時,裏面沒有一個線程。任務隊列是作爲參數傳進來的。即使隊列裏面有任務,線程池也不會馬上執行它們。②通過 execute(Runnable command)方法被添加到線程池,任務就是一個 Runnable類型的對象,任務的執行方法就是Runnable類型對象的run()方法。③如果workerCount<corePoolSize,那麼創建並啓動一個線程執行新提交的任務。如果workerCount>=corePoolSize,且線程池內的阻塞隊列未滿,那麼將這個任務放入隊列。如果workerCount>=corePoolSize,且阻塞隊列已滿,若滿足workerCount<maximumPoolSize,那麼還是要創建並啓動一個線程執行新提交的任務。若阻塞隊列已滿,並且workerCount>=maximumPoolSize,則根據 handler所指定的策略來處理此任務,默認的處理方式直接拋出異常。也就是處理任務的優先級爲: 核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。④當一個線程完成任務時,它會從隊列中取下一個任務來執行。⑤當一個線程沒有任務可執行,超過一定的時間(keepAliveTime)時,線程池會判斷,如果當前運行的線程數大於corePoolSize時,那麼這個線程會被停用掉,所以線程池的所有任務完成後,它最終會收縮到corePoolSize的大小。

Q28:簡述ExecutorService的生命週期
答:①ExecutorService的生命週期有3種狀態:運行、關閉和已終止。②ExecutorService在初始創建時處於運行狀態。③shutdown方法將執行平緩的關閉過程:不再接受新的任務,同時等待已經提交的任務執行完成——包括那些還未開始執行的任務。shutdownNow方法將執行粗暴的關閉過程:它將嘗試取消所有運行中的任務,並且不再啓動隊列中尚未開始執行的任務。在ExecutorService關閉後提交的任務將有「拒絕執行處理器REH」來處理,它會拋棄任務,或者使得execute方法拋出一個未檢查的RejectedExecutionException。④等所有任務都完成後,ExecutorService將轉入終止狀態。可以調用awaitTermination來等待ExecutorService到達終止狀態,或者通過調用isTerminated來輪詢ExecutorService是否已終止。通常在調用awaitTermination後會理解調用shutdown,從而產生同步地關閉ExecutorService的效果。

Q29:什麼是CAS?
答:①CAS指Compare And Swap,比較並交換。CAS(V,E,N)算法包括三個參數,V表示要更新的變量的值,E表示預期的值,N表示新值。在且僅在V的值和E相等時纔會將V的值設置爲N,如果不同則說明已經有其他線程做了更改,當前線程就什麼也不做。最後CAS返回當前V的真實值。②CAS操作採用了樂觀鎖的思想,有多個線程同時使用CAS操作一個共享變量時只有一個線程會成功,失敗的線程不會被掛起僅會被告知失敗,並且允許再次嘗試,或者放棄操作。基於這樣的原理雖然CAS沒有使用鎖,也可以及時發現其他線程的操作進行適當地併發處理。

Q30:CAS有什麼問題?(什麼是ABA問題?)如何解決?
答:①CAS算法地實現有一個重要前提:需要取出內存中某時刻的數據,然後在下一刻進行比較、替換,但在這個時間差內數據可能已經發生了變化,導致ABA問題。②ABA問題指線程1從內存V位置取出A,這時線程2也從內存中取出A,並將其首先修改爲B,接着又修改爲A,這時線程1在進行CAS操作時會發現內存中數據仍是A,然後線程1操作成功。儘管從操作角度來說線程1成功了,但是在該過程中實際上數據已發生了變化但並未被感知到,某些應用場景下可能會出現數據不一致的問題。③樂觀鎖通過版本號來解決ABA問題,具體的操作是每次執行數據修改操作時都會帶上一個版本號,如果預期版本號和數據版本號一致就進行操作,並將版本號加1,否�也可以及時發現其他線程的操作進行適當地併發處理。

Q30:CAS有什麼問題?(什麼是ABA問題?)如何解決?
答:①CAS算法地實現有一個重要前提:需要取出內存中某時刻的數據,然後在下一刻進行比較、替換,但在這個時間差內數據可能已經發生了變化,導致ABA問題。②ABA問題指線程1從內存V位置取出A,這時線程2也從內存中取出A,並將其首先修改爲B,接着又修改爲A,這時線程1在進行CAS操作時會發現內存中數據仍是A,然後線程1操作成功。儘管從操作角度來說線程1成功了,但是在該過程中實際上數據已發生了變化但並未被感知到,某些應用場景下可能會出現數據不一致的問題。③樂觀鎖通過版本號來解決ABA問題,具體的操作是每次執行數據修改操作時都會帶上一個版本號,如果預期版本號和數據版本號一致就進行操作,並將版本號加1,否則執行失敗。

Q31:講一講wait、sleep、yield、join方法的區別
答:①wait是Object類的方法,調用wait方法的線程會進入WAITING狀態,只有等待其他線程的通知或被中斷後纔會解除阻塞,調用wait方法會釋放鎖資源。②sleep是Thread類的方法,調用sleep方法會導致當前線程進入休眠狀態,與wait不同的是該方法不會釋放鎖資源,進入的是TIMED-WAITING狀態。③yiled方法會使當前線程讓出CPU時間片給優先級相同或更高的線程,回到RUNNABLE狀態,與其他線程一起重新競爭CPU時間片。④join方法用於等待其他線程運行終止,如果當前線程調用了另一個線程的join方法,則當前線程進入阻塞狀態,當另一個線程結束時當前線程才能從阻塞狀態轉爲就緒態,等待獲取CPU時間片。底層使用的是wait,也會釋放鎖。

Q32:講一講線程中斷
答:①interrupt方法用於向線程發送一個終止信號,會影響該線程內部的中斷標識位,這個線程本身不會因爲調用了interrupt方法而改變狀態,狀態的具體變化需要等待接收到中斷標識的程序的處理結果判定。②調用interrupt方法不會中斷一個正在運行的線程,只會改變內部的中斷標識位的值爲true。③當調用sleep方法使線程處於TIMED-WAITING狀態使,調用interrupt方法會拋出InterruptedException,使線程提前結束TIMED-WAITING狀態。在拋出該異常前將清除中斷標識位,所以在拋出異常後調用isInterrupted方法返回的值是false。④中斷狀態是線程固有的一個標識位,可以通過此標識位安全終止線程。比如想終止某個線程時,先調用interrupt方法然後在run方法中根據該線程isInterrupted方法的返回值安全終止線程。

Q33:什麼是守護線程?
答:①守護線程是運行在後臺的一種特殊線程,獨立於控制終端並且週期性地執行某種任務或等待處理某些已發生的事件。守護線程不依賴於終端,但是依賴於JVM,當JVM中僅剩下守護線程時,JVM就會退出。②通過setDaemon方法定義一個守護線程,守護線程的優先級較低,將一個用戶線程設置爲守護線程必須要在啓動守護線程之前。

Q34:start和run方法的區別?
答:①start方法用於啓動線程,真正實現了多線程,調用了start方法後,會在後臺創建一個新的線程來執行,不需要等待run方法執行完畢就可以繼續執行其他代碼。調用start方法時,該線程處於就緒狀態,並沒有開始運行。②run方法也叫做線程體,包含了要執行的線程的邏輯代碼,在調用run方法並沒有創建新的線程,而是直接運行run方法中的代碼。


JVM 15

Q1:類的加載機制是什麼?
答:類加載到內存中主要有5個階段,分別爲①加載:將Class文件讀取到運行時數據區的方法區內,在堆中創建Class對象,並封裝類在方法區的數據結構的過程。②驗證:主要用於確保Class文件符合當前虛擬機的要求,保障虛擬機自身的安全,只有通過驗證的Class文件才能被JVM加載。③準備:主要工作是在方法區中爲類變量分配內存空間並設置類中變量的初始值。④解析:將常量池中的符號引用替換爲直接引用。⑤初始化:主要通過執行類構造器的<client>方法爲類進行初始化,該方法是在編譯階段由編譯器自動收集類中靜態語句塊和變量的賦值操作組成的。JVM規定,只有在父類的<client>方法都執行成功後,子類的方法纔可以被執行。在一個類中既沒有靜態變量賦值操作也沒有靜態語句塊時,編譯器不會爲該類生成<client>方法。

Q2:有哪些類加載器,類加載器的加載模型是什麼,有什麼好處?
答:①主要有啓動類加載器,負責加載JAVA_HOME/lib中的類庫;擴展類加載器,負責加載JAVA_HOME/lib/ext中的類庫;應用程序類加載器,也稱系統類加載器,負責加載用戶類路徑上指定的類庫;也可以自定義類加載器。②類加載器之間的層次關係叫做雙親委派模型,要求除了頂層的啓動類加載器外其餘的類加載器都應當有自己的父類加載器。一個類收到類加載請求後會層層找父類加載器去嘗試加載,因此所有的加載請求最終都會被傳送到頂層的啓動類加載器,只有當父類加載器反饋自己無法完成加載時子加載器纔會嘗試自己去加載。③雙親委派模型的好處是保障類加載的唯一性和安全性,例如加載rt.jar包中的java.lang.Object,無論哪一個類加載最終都會委託給啓動類加載器,這樣就保證了類加載的唯一性。如果存在包名和類名都相同的兩個類,那麼該類就無法被加載。

Q3:簡述JVM的內存區域
答:JVM的內存區域分爲線程私有區域(程序計數器、虛擬機棧、本地方法區)、線程共享區域(堆、方法區)和直接內存。①程序計數器是一塊很小的內存空間,用於存儲當前線程執行字節碼文件的行號指示器。②虛擬機棧是描述Java方法執行過程的內存模型,幀棧中存儲了局部變量表,操作數棧,動態鏈接,方法出口等信息。③本地方法棧,和虛擬機棧作用類似,區別是虛擬機棧爲Java方法服務,本地方法棧爲Native方法服務。④JVM運行過程中創建的對象和生成的數據都存儲在堆中,堆是被線程共享的內存區域,也是垃圾回收最主要的內存區域。⑤方法區用來存儲常量,靜態變量、類信息、即時編譯器編譯後的機器碼、運行時常量池等數據。

Q4:哪些情況下類不會初始化?
答:①常量在編譯時會存放在使用該常量的類的常量池,該過程不要調用常量所在的類,不會初始化。②子類引用父類的靜態變量時,子類不會初始化,只有父類會初始化。③定義對象數組,不會觸發該類的初始化。④在使用類名獲取Class對象時不會觸發類的初始化。⑤在使用Class.forName()加載指定的類時,可以通過initialize參數設置是否需要初始化。⑥在使用ClassLoader默認的loadClass方法加載類時不會觸發該類的初始化。

Q5:哪些情況下類會初始化?
答:①創建類的實例。②訪問某個類或接口的靜態變量,或對該靜態變量賦值。③調用類的靜態方法。④初始化一個類的子類時(初始化子類,父類必須先初始化)。⑤JVM啓動時被標爲啓動類的類。⑥使用反射進行方法調用時。

Q6:談談JVM的運行時內存
答:JVM的運行時內存也叫做JVM堆,從GC角度更將其分爲新生代,老年代和永久代。
其中新生代默認佔1/3堆空間,老年代默認佔2/3堆空間,永久代佔非常少的堆空間。
新生代又分爲Eden區、ServivorFrom區和ServivorTo區,Eden區默認佔8/10新生代空間,ServivorFrom區和ServivorTo區默認分別佔1/10新生代空間。

Q7:談談新生代是怎麼分區的
答:①JVM新創建的對象(除了大對象外)會被存放在新生代,默認佔1/3堆內存空間。由於JVM會頻繁創建對象,所以新生代會頻繁觸發MinorGC進行垃圾回收。②新生代又分爲Eden區,ServivorFrom區和ServivorTo區。③Ede,會在後臺創建一個新的線程來執行,不需要等待run方法執行完畢就可以繼續執行其他代碼。調用start方法時,該線程處於就緒狀態,並沒有開始運行。②run方法也叫做線程體,包含了要執行的線程的邏輯代碼,在調用run方法並沒有創建新的線程,而是直接運行run方法中的代碼。


JVM 15

Q1:類的加載機制是什麼?
答:類加載到內存中主要有5個階段,分別爲①加載:將Class文件讀取到運行時數據區的方法區內,在堆中創建Class對象,並封裝類在方法區的數據結構的過程。②驗證:主要用於確保Class文件符合當前虛擬機的要求,保障虛擬機自身的安全,只有通過驗證的Class文件才能被JVM加載。③準備:主要工作是在方法區中爲類變量分配內存空間並設置類中變量的初始值。④解析:將常量池中的符號引用替換爲直接引用。⑤初始化:主要通過執行類構造器的<client>方法爲類進行初始化,該方法是在編譯階段由編譯器自動收集類中靜態語句塊和變量的賦值操作組成的。JVM規定,只有在父類的<client>方法都執行成功後,子類的方法纔可以被執行。在一個類中既沒有靜態變量賦值操作也沒有靜態語句塊時,編譯器不會爲該類生成<client>方法。

Q2:有哪些類加載器,類加載器的加載模型是什麼,有什麼好處?
答:①主要有啓動類加載器,負責加載JAVA_HOME/lib中的類庫;擴展類加載器,負責加載JAVA_HOME/lib/ext中的類庫;應用程序類加載器,也稱系統類加載器,負責加載用戶類路徑上指定的類庫;也可以自定義類加載器。②類加載器之間的層次關係叫做雙親委派模型,要求除了頂層的啓動類加載器外其餘的類加載器都應當有自己的父類加載器。一個類收到類加載請求後會層層找父類加載器去嘗試加載,因此所有的加載請求最終都會被傳送到頂層的啓動類加載器,只有當父類加載器反饋自己無法完成加載時子加載器纔會嘗試自己去加載。③雙親委派模型的好處是保障類加載的唯一性和安全性,例如加載rt.jar包中的java.lang.Object,無論哪一個類加載最終都會委託給啓動類加載器,這樣就保證了類加載的唯一性。如果存在包名和類名都相同的兩個類,那麼該類就無法被加載。

Q3:簡述JVM的內存區域
答:JVM的內存區域分爲線程私有區域(程序計數器、虛擬機棧、本地方法區)、線程共享區域(堆、方法區)和直接內存。①程序計數器是一塊很小的內存空間,用於存儲當前線程執行字節碼文件的行號指示器。②虛擬機棧是描述Java方法執行過程的內存模型,幀棧中存儲了局部變量表,操作數棧,動態鏈接,方法出口等信息。③本地方法棧,和虛擬機棧作用類似,區別是虛擬機棧爲Java方法服務,本地方法棧爲Native方法服務。④JVM運行過程中創建的對象和生成的數據都存儲在堆中,堆是被線程共享的內存區域,也是垃圾回收最主要的內存區域。⑤方法區用來存儲常量,靜態變量、類信息、即時編譯器編譯後的機器碼、運行時常量池等數據。

Q4:哪些情況下類不會初始化?
答:①常量在編譯時會存放在使用該常量的類的常量池,該過程不要調用常量所在的類,不會初始化。②子類引用父類的靜態變量時,子類不會初始化,只有父類會初始化。③定義對象數組,不會觸發該類的初始化。④在使用類名獲取Class對象時不會觸發類的初始化。⑤在使用Class.forName()加載指定的類時,可以通過initialize參數設置是否需要初始化。⑥在使用ClassLoader默認的loadClass方法加載類時不會觸發該類的初始化。

Q5:哪些情況下類會初始化?
答:①創建類的實例。②訪問某個類或接口的靜態變量,或對該靜態變量賦值。③調用類的靜態方法。④初始化一個類的子類時(初始化子類,父類必須先初始化)。⑤JVM啓動時被標爲啓動類的類。⑥使用反射進行方法調用時。

Q6:談談JVM的運行時內存
答:JVM的運行時內存也叫做JVM堆,從GC角度更將其分爲新生代,老年代和永久代。
其中新生代默認佔1/3堆空間,老年代默認佔2/3堆空間,永久代佔非常少的堆空間。
新生代又分爲Eden區、ServivorFrom區和ServivorTo區,Eden區默認佔8/10新生代空間,ServivorFrom區和ServivorTo區默認分別佔1/10新生代空間。

Q7:談談新生代是怎麼分區的
答:①JVM新創建的對象(除了大對象外)會被存放在新生代,默認佔1/3堆內存空間。由於JVM會頻繁創建對象,所以新生代會頻繁觸發MinorGC進行垃圾回收。②新生代又分爲Eden區,ServivorFrom區和ServivorTo區。③Eden區:Java新創建的對象首先會被存放在Eden�,會在後臺創建一個新的線程來執行,不需要等待run方法執行完畢就可以繼續執行其他代碼。調用start方法時,該線程處於就緒狀態,並沒有開始運行。②run方法也叫做線程體,包含了要執行的線程的邏輯代碼,在調用run方法並沒有創建新的線程,而是直接運行run方法中的代碼。


JVM 15

Q1:類的加載機制是什麼?
答:類加載到內存中主要有5個階段,分別爲①加載:將Class文件讀取到運行時數據區的方法區內,在堆中創建Class對象,並封裝類在方法區的數據結構的過程。②驗證:主要用於確保Class文件符合當前虛擬機的要求,保障虛擬機自身的安全,只有通過驗證的Class文件才能被JVM加載。③準備:主要工作是在方法區中爲類變量分配內存空間並設置類中變量的初始值。④解析:將常量池中的符號引用替換爲直接引用。⑤初始化:主要通過執行類構造器的<client>方法爲類進行初始化,該方法是在編譯階段由編譯器自動收集類中靜態語句塊和變量的賦值操作組成的。JVM規定,只有在父類的<client>方法都執行成功後,子類的方法纔可以被執行。在一個類中既沒有靜態變量賦值操作也沒有靜態語句塊時,編譯器不會爲該類生成<client>方法。

Q2:有哪些類加載器,類加載器的加載模型是什麼,有什麼好處?
答:①主要有啓動類加載器,負責加載JAVA_HOME/lib中的類庫;擴展類加載器,負責加載JAVA_HOME/lib/ext中的類庫;應用程序類加載器,也稱系統類加載器,負責加載用戶類路徑上指定的類庫;也可以自定義類加載器。②類加載器之間的層次關係叫做雙親委派模型,要求除了頂層的啓動類加載器外其餘的類加載器都應當有自己的父類加載器。一個類收到類加載請求後會層層找父類加載器去嘗試加載,因此所有的加載請求最終都會被傳送到頂層的啓動類加載器,只有當父類加載器反饋自己無法完成加載時子加載器纔會嘗試自己去加載。③雙親委派模型的好處是保障類加載的唯一性和安全性,例如加載rt.jar包中的java.lang.Object,無論哪一個類加載最終都會委託給啓動類加載器,這樣就保證了類加載的唯一性。如果存在包名和類名都相同的兩個類,那麼該類就無法被加載。

Q3:簡述JVM的內存區域
答:JVM的內存區域分爲線程私有區域(程序計數器、虛擬機棧、本地方法區)、線程共享區域(堆、方法區)和直接內存。①程序計數器是一塊很小的內存空間,用於存儲當前線程執行字節碼文件的行號指示器。②虛擬機棧是描述Java方法執行過程的內存模型,幀棧中存儲了局部變量表,操作數棧,動態鏈接,方法出口等信息。③本地方法棧,和虛擬機棧作用類似,區別是虛擬機棧爲Java方法服務,本地方法棧爲Native方法服務。④JVM運行過程中創建的對象和生成的數據都存儲在堆中,堆是被線程共享的內存區域,也是垃圾回收最主要的內存區域。⑤方法區用來存儲常量,靜態變量、類信息、即時編譯器編譯後的機器碼、運行時常量池等數據。

Q4:哪些情況下類不會初始化?
答:①常量在編譯時會存放在使用該常量的類的常量池,該過程不要調用常量所在的類,不會初始化。②子類引用父類的靜態變量時,子類不會初始化,只有父類會初始化。③定義對象數組,不會觸發該類的初始化。④在使用類名獲取Class對象時不會觸發類的初始化。⑤在使用Class.forName()加載指定的類時,可以通過initialize參數設置是否需要初始化。⑥在使用ClassLoader默認的loadClass方法加載類時不會觸發該類的初始化。

Q5:哪些情況下類會初始化?
答:①創建類的實例。②訪問某個類或接口的靜態變量,或對該靜態變量賦值。③調用類的靜態方法。④初始化一個類的子類時(初始化子類,父類必須先初始化)。⑤JVM啓動時被標爲啓動類的類。⑥使用反射進行方法調用時。

Q6:談談JVM的運行時內存
答:JVM的運行時內存也叫做JVM堆,從GC角度更將其分爲新生代,老年代和永久代。
其中新生代默認佔1/3堆空間,老年代默認佔2/3堆空間,永久代佔非常少的堆空間。
新生代又分爲Eden區、ServivorFrom區和ServivorTo區,Eden區默認佔8/10新生代空間,ServivorFrom區和ServivorTo區默認分別佔1/10新生代空間。

Q7:談談新生代是怎麼分區的
答:①JVM新創建的對象(除了大對象外)會被存放在新生代,默認佔1/3堆內存空間。由於JVM會頻繁創建對象,所以新生代會頻繁觸發MinorGC進行垃圾回收。②新生代又分爲Eden區,ServivorFrom區和ServivorTo區。③Eden區:Java新創建的對象首先會被存放在Eden區,如果新創建的對象屬於大對象,則直接將其分配到老年代。大對象的定義和具體的JVM版本、堆大小和垃圾回收策略有關,一般爲2KB~128KB,可通過-XX:PretenureSizeThreshold設置其大小。在Eden區的內存空間不足時會觸發MinorGC,對新生代進行一次垃圾回收。②ServivorTo區:保留上一次MinorGC時的倖存者。③ServivorFrom區:將上一次MinorGC時的倖存者作爲這一次MinorGC的被掃描者。

Q8:談談新生代的垃圾回收機制
答:新生代的GC過程叫做MinorGC,採用複製算法實現,具體過程如下:
①把在Eden區和ServivorFrom區中存活的對象複製到ServivorTo區,如果某對象的年齡達到老年代的標準,則將其複製到老年代,同時把這些對象的年齡加1。如果ServivorTo區的內存空間不夠,則也直接將其複製到老年代。如果對象屬於大對象,則也直接複製到老年代。②清空Eden區和ServivorFrom區中的對象。③將ServivorFrom區和ServivorTo區互換,原來的ServivorTo區成爲下一次GC時的ServivorFrom區。

Q9:談談老年代的垃圾回收機制
答:①老年代主要存放有長生命週期的對象和大對象,老年代的GC叫MajorGC。②在老年代,對象比較穩定,MajorGC不會頻繁觸發。在進行MajorGC前,JVM會進行一次MinorGC,過後仍然出現老年代空間不足或無法找到足夠大的連續內存空間分配給新創建的大對象時,會觸發MajorGC進行垃圾回收,釋放JVM的內存空間。③MajorGC採用標記清除算法,該算法首先會掃描所有對象並標記存活的對象,然後回收未被標記的對象,並釋放內存空間。因爲要先掃描老年代的所有對象再回收,所以MajorGC的時間較長。容易產生內存碎片,在老年代沒有內存空間可分配時,會出現內存溢出異常。

Q10:談一談永久代
答:①永久代指內存的永久保存區域,主要存放Class和Meta(元數據)的信息。Class在類加載時被放入永久代。②永久代和老年代、新生代不同,GC不會在程序運行期間對永久代的內存進行清理,這也導致了永久代的內存會隨着加載的Class文件的增加而增加,在加載的Class文件過多時會出現內存溢出異常,比如Tomcat引用jar文件過多導致JVM內存不足而無法啓動。③在JDK1.8中,永久代已經被元數據區取代。元數據區的作用和永久代類似,二者最大的區別在於:元數據區並沒有使用虛擬機的內存,而是直接使用操作哪些類加載器,類加載器的加載模型是什麼,有什麼好處?
答:①主要有啓動類加載器,負責加載JAVA_HOME/lib中的類庫;擴展類加載器,負責加載JAVA_HOME/lib/ext中的類庫;應用程序類加載器,也稱系統類加載器,負責加載用戶類路徑上指定的類庫;也可以自定義類加載器。②類加載器之間的層次關係叫做雙親委派模型,要求除了頂層的啓動類加載器外其餘的類加載器都應當有自己的父類加載器。一個類收到類加載請求後會層層找父類加載器去嘗試加載,因此所有的加載請求最終都會被傳送到頂層的啓動類加載器,只有當父類加載器反饋自己無法完成加載時子加載器纔會嘗試自己去加載。③雙親委�哪些類加載器,類加載器的加載模型是什麼,有什麼好處?
答:①主要有啓動類加載器,負責加載JAVA_HOME/lib中的類庫;擴展類加載器,負責加載JAVA_HOME/lib/ext中的類庫;應用程序類加載器,也稱系統類加載器,負責加載用戶類路徑上指定的類庫;也可以自定義類加載器。②類加載器之間的層次關係叫做雙親委派模型,要求除了頂層的啓動類加載器外其餘的類加載器都應當有自己的父類加載器。一個類收到類加載請求後會層層找父類加載器去嘗試加載,因此所有的加載請求最終都會被傳送到頂層的啓動類加載器,只有當父類加載器反饋自己無法完成加載時子加載器纔會嘗試自己去加載。③雙親委派模型的好處是保障類加載的唯一性和安全性,例如加載rt.jar包中的java.lang.Object,無論哪一個類加載最終都會委託給啓動類加載器,這樣就保證了類加載的唯一性。如果存在包名和類名都相同的兩個類,那麼該類就無法被加載。

Q3:簡述JVM的內存區域
答:JVM的內存區域分爲線程私有區域(程序計數器、虛擬機棧、本地方法區)、線程共享區域(堆、方法區)和直接內存。①程序計數器是一塊很小的內存空間,用於存儲當前線程執行字節碼文件的行號指示器。②虛擬機棧是描述Java方法執行過程的內存模型,幀棧中存儲了局部變量表,操作數棧,動態鏈接,方法出口等信息。③本地方法棧,和虛擬機棧作用類似,區別是虛擬機棧爲Java方法服務,本地方法棧爲Native方法服務。④JVM運行過程中創建的對象和生成的數據都存儲在堆中,堆是被線程共享的內存區域,也是垃圾回收最主要的內存區域。⑤方法區用來存儲常量,靜態變量、類信息、即時編譯器編譯後的機器碼、運行時常量池等數據。

Q4:哪些情況下類不會初始化?
答:①常量在編譯時會存放在使用該常量的類的常量池,該過程不要調用常量所在的類,不會初始化。②子類引用父類的靜態變量時,子類不會初始化,只有父類會初始化。③定義對象數組,不會觸發該類的初始化。④在使用類名獲取Class對象時不會觸發類的初始化。⑤在使用Class.forName()加載指定的類時,可以通過initialize參數設置是否需要初始化。⑥在使用ClassLoader默認的loadClass方法加載類時不會觸發該類的初始化。

Q5:哪些情況下類會初始化?
答:①創建類的實例。②訪問某個類或接口的靜態變量,或對該靜態變量賦值。③調用類的靜態方法。④初始化一個類的子類時(初始化子類,父類必須先初始化)。⑤JVM啓動時被標爲啓動類的類。⑥使用反射進行方法調用時。

Q6:談談JVM的運行時內存
答:JVM的運行時內存也叫做JVM堆,從GC角度更將其分爲新生代,老年代和永久代。
其中新生代默認佔1/3堆空間,老年代默認佔2/3堆空間,永久代佔非常少的堆空間。
新生代又分爲Eden區、ServivorFrom區和ServivorTo區,Eden區默認佔8/10新生代空間,ServivorFrom區和ServivorTo區默認分別佔1/10新生代空間。

Q7:談談新生代是怎麼分區的
答:①JVM新創建的對象(除了大對象外)會被存放在新生代,默認佔1/3堆內存空間。由於JVM會頻繁創建對象,所以新生代會頻繁觸發MinorGC進行垃圾回收。②新生代又分爲Eden區,ServivorFrom區和ServivorTo區。③Eden區:Java新創建的對象首先會被存放在Eden區,如果新創建的對象屬於大對象,則直接將其分配到老年代。大對象的定義和具體的JVM版本、堆大小和垃圾回收策略有關,一般爲2KB~128KB,可通過-XX:PretenureSizeThreshold設置其大小。在Eden區的內存空間不足時會觸發MinorGC,對新生代進行一次垃圾回收。②ServivorTo區:保留上一次MinorGC時的倖存者。③ServivorFrom區:將上一次MinorGC時的倖存者作爲這一次MinorGC的被掃描者。

Q8:談談新生代的垃圾回收機制
答:新生代的GC過程叫做MinorGC,採用複製算法實現,具體過程如下:
①把在Eden區和ServivorFrom區中存活的對象複製到ServivorTo區,如果某對象的年齡達到老年代的標準,則將其複製到老年代,同時把這些對象的年齡加1。如果ServivorTo區的內存空間不夠,則也直接將其複製到老年代。如果對象屬於大對象,則也直接複製到老年代。②清空Eden區和ServivorFrom區中的對象。③將ServivorFrom區和ServivorTo區互換,原來的ServivorTo區成爲下一次GC時的ServivorFrom區。

Q9:談談老年代的垃圾回收機制
答:①老年代主要存放有長生命週期的對象和大對象,老年代的GC叫MajorGC。②在老年代,對象比較穩定,MajorGC不會頻繁觸發。在進行MajorGC前,JVM會進行一次MinorGC,過後仍然出現老年代空間不足或無法找到足夠大的連續內存空間分配給新創建的大對象時,會觸發MajorGC進行垃�哪些類加載器,類加載器的加載模型是什麼,有什麼好處?
答:①主要有啓動類加載器,負責加載JAVA_HOME/lib中的類庫;擴展類加載器,負責加載JAVA_HOME/lib/ext中的類庫;應用程序類加載器,也稱系統類加載器,負責加載用戶類路徑上指定的類庫;也可以自定義類加載器。②類加載器之間的層次關係叫做雙親委派模型,要求除了頂層的啓動類加載器外其餘的類加載器都應當有自己的父類加載器。一個類收到類加載請求後會層層找父類加載器去嘗試加載,因此所有的加載請求最終都會被傳送到頂層的啓動類加載器,只有當父類加載器反饋自己無法完成加載時子加載器纔會嘗試自己去加載。③雙親委派模型的好處是保障類加載的唯一性和安全性,例如加載rt.jar包中的java.lang.Object,無論哪一個類加載最終都會委託給啓動類加載器,這樣就保證了類加載的唯一性。如果存在包名和類名都相同的兩個類,那麼該類就無法被加載。

Q3:簡述JVM的內存區域
答:JVM的內存區域分爲線程私有區域(程序計數器、虛擬機棧、本地方法區)、線程共享區域(堆、方法區)和直接內存。①程序計數器是一塊很小的內存空間,用於存儲當前線程執行字節碼文件的行號指示器。②虛擬機棧是描述Java方法執行過程的內存模型,幀棧中存儲了局部變量表,操作數棧,動態鏈接,方法出口等信息。③本地方法棧,和虛擬機棧作用類似,區別是虛擬機棧爲Java方法服務,本地方法棧爲Native方法服務。④JVM運行過程中創建的對象和生成的數據都存儲在堆中,堆是被線程共享的內存區域,也是垃圾回收最主要的內存區域。⑤方法區用來存儲常量,靜態變量、類信息、即時編譯器編譯後的機器碼、運行時常量池等數據。

Q4:哪些情況下類不會初始化?
答:①常量在編譯時會存放在使用該常量的類的常量池,該過程不要調用常量所在的類,不會初始化。②子類引用父類的靜態變量時,子類不會初始化,只有父類會初始化。③定義對象數組,不會觸發該類的初始化。④在使用類名獲取Class對象時不會觸發類的初始化。⑤在使用Class.forName()加載指定的類時,可以通過initialize參數設置是否需要初始化。⑥在使用ClassLoader默認的loadClass方法加載類時不會觸發該類的初始化。

Q5:哪些情況下類會初始化?
答:①創建類的實例。②訪問某個類或接口的靜態變量,或對該靜態變量賦值。③調用類的靜態方法。④初始化一個類的子類時(初始化子類,父類必須先初始化)。⑤JVM啓動時被標爲啓動類的類。⑥使用反射進行方法調用時。

Q6:談談JVM的運行時內存
答:JVM的運行時內存也叫做JVM堆,從GC角度更將其分爲新生代,老年代和永久代。
其中新生代默認佔1/3堆空間,老年代默認佔2/3堆空間,永久代佔非常少的堆空間。
新生代又分爲Eden區、ServivorFrom區和ServivorTo區,Eden區默認佔8/10新生代空間,ServivorFrom區和ServivorTo區默認分別佔1/10新生代空間。

Q7:談談新生代是怎麼分區的
答:①JVM新創建的對象(除了大對象外)會被存放在新生代,默認佔1/3堆內存空間。由於JVM會頻繁創建對象,所以新生代會頻繁觸發MinorGC進行垃圾回收。②新生代又分爲Eden區,ServivorFrom區和ServivorTo區。③Eden區:Java新創建的對象首先會被存放在Eden區,如果新創建的對象屬於大對象,則直接將其分配到老年代。大對象的定義和具體的JVM版本、堆大小和垃圾回收策略有關,一般爲2KB~128KB,可通過-XX:PretenureSizeThreshold設置其大小。在Eden區的內存空間不足時會觸發MinorGC,對新生代進行一次垃圾回收。②ServivorTo區:保留上一次MinorGC時的倖存者。③ServivorFrom區:將上一次MinorGC時的倖存者作爲這一次MinorGC的被掃描者。

Q8:談談新生代的垃圾回收機制
答:新生代的GC過程叫做MinorGC,採用複製算法實現,具體過程如下:
①把在Eden區和ServivorFrom區中存活的對象複製到ServivorTo區,如果某對象的年齡達到老年代的標準,則將其複製到老年代,同時把這些對象的年齡加1。如果ServivorTo區的內存空間不夠,則也直接將其複製到老年代。如果對象屬於大對象,則也直接複製到老年代。②清空Eden區和ServivorFrom區中的對象。③將ServivorFrom區和ServivorTo區互換,原來的ServivorTo區成爲下一次GC時的ServivorFrom區。

Q9:談談老年代的垃圾回收機制
答:①老年代主要存放有長生命週期的對象和大對象,老年代的GC叫MajorGC。②在老年代,對象比較穩定,MajorGC不會頻繁觸發。在進行MajorGC前,JVM會進行一次MinorGC,過後仍然出現老年代空間不足或無法找到足夠大的連續內存空間分配給新創建的大對象時,會觸發MajorGC進行垃圾回收,釋放JVM的內存空間。③MajorGC採用標記清除算法,該算法首先會掃描所有對象並標記存活的對象,然後回收未被標記的對象,並釋放內存空間。因爲要先掃描老年代的所有對象再回收,所以MajorGC的時間較長。容易產生內存碎片,在老年代沒有內存空間可分配時,會出現內存溢出異常。

Q10:談一談永久代
答:①永久代指內存的永久保存區域,主要存放Class和Meta(元數據)的信息。Class在類加載時被放入永久代。②永久代和老年代、新生代不同,GC不會在程序運行期間對永久代的內存進行清理,這也導致了永久代的內存會隨着加載的Class文件的增加而增加,在加載的Class文件過多時會出現內存溢出異常,比如Tomcat引用jar文件過多導致JVM內存不足而無法啓動。③在JDK1.8中,永久代已經被元數據區取代。元數據區的作用和永久代類似,二者最大的區別在於:元數據區並沒有使用虛擬機的內存,而是直接使用操作系統的本地內存。因此元空間的大小不受JVM內存的限制,只和操作系統的內存有關。④在JDK1.8中,JVM將類的元數據放入本地內存中,將常量池和類的靜態常量放入Java堆中,這樣JVM能夠加載多少元數據信息就不再由JVM的最大可用內存空間決定,而由操作系統的實際可用內存空間決定。

Q11:如何確定對象是否是垃圾?
答:①Java採用引用計數法可達性分析來確定對象是否應該被回收。引用計數法容易產生循環引用的問題,可達性分析通過根搜索算法實現。根搜索算法以一系列GC Roots的點作爲起點向下搜索,在一個對象到任何GC Roots都沒有引用鏈相連時,說明其已經死亡。根搜索算法主要針對棧中的引用、方法區的靜態引用和JNI中的引用展開分析。②引用計數法:在Java中如果要操作對象,就必須先獲取該對象的引用,因此可以通過引用計數法來判斷一個對象是否可以被回收。在爲對象添加一個引用時,引用計數加1;在爲對象刪除一個引用時,引用計數減1;如果一個對象的引用計數爲0,則表示此刻該對象沒有被引用,可以被回收。引用計數法容易產生循環引用問題,循環引用指兩個對象相互引用,導致它們的引用一直存在,而不能被回收。③可達性分析:爲了解決引用計數法的循環引用問題,Java還採用了可達性分析來判斷對象是否可以被回收。具哪些類加載器,類加載器的加載模型是什麼,有什麼好處?
答:①主要有啓動類加載器,負責加載JAVA_HOME/lib中的類庫;擴展類加載器,負責加載JAVA_HOME/lib/ext中的類庫;應用程序類加載器,也稱系統類加載器,負責加載用戶類路徑上指定的類庫;也可以自定義類加載器。②類加載器之間的層次關係叫做雙親委派模型,要求除了頂層的啓動類加載器外其餘的類加載器都應當有自己的父類加載器。一個類收到類加載請求後會層層找父類加載器去嘗試加載,因此所有的加載請求最終都會被傳送到頂層的啓動類加載器,只有當父類加載器反饋自己無法完成加載時子加載器纔會嘗試自己去加載。③雙親委�哪些類加載器,類加載器的加載模型是什麼,有什麼好處?
答:①主要有啓動類加載器,負責加載JAVA_HOME/lib中的類庫;擴展類加載器,負責加載JAVA_HOME/lib/ext中的類庫;應用程序類加載器,也稱系統類加載器,負責加載用戶類路徑上指定的類庫;也可以自定義類加載器。②類加載器之間的層次關係叫做雙親委派模型,要求除了頂層的啓動類加載器外其餘的類加載器都應當有自己的父類加載器。一個類收到類加載請求後會層層找父類加載器去嘗試加載,因此所有的加載請求最終都會被傳送到頂層的啓動類加載器,只有當父類加載器反饋自己無法完成加載時子加載器纔會嘗試自己去加載。③雙親委派模型的好處是保障類加載的唯一性和安全性,例如加載rt.jar包中的java.lang.Object,無論哪一個類加載最終都會委託給啓動類加載器,這樣就保證了類加載的唯一性。如果存在包名和類名都相同的兩個類,那麼該類就無法被加載。

Q3:簡述JVM的內存區域
答:JVM的內存區域分爲線程私有區域(程序計數器、虛擬機棧、本地方法區)、線程共享區域(堆、方法區)和直接內存。①程序計數器是一塊很小的內存空間,用於存儲當前線程執行字節碼文件的行號指示器。②虛擬機棧是描述Java方法執行過程的內存模型,幀棧中存儲了局部變量表,操作數棧,動態鏈接,方法出口等信息。③本地方法棧,和虛擬機棧作用類似,區別是虛擬機棧爲Java方法服務,本地方法棧爲Native方法服務。④JVM運行過程中創建的對象和生成的數據都存儲在堆中,堆是被線程共享的內存區域,也是垃圾回收最主要的內存區域。⑤方法區用來存儲常量,靜態變量、類信息、即時編譯器編譯後的機器碼、運行時常量池等數據。

Q4:哪些情況下類不會初始化?
答:①常量在編譯時會存放在使用該常量的類的常量池,該過程不要調用常量所在的類,不會初始化。②子類引用父類的靜態變量時,子類不會初始化,只有父類會初始化。③定義對象數組,不會觸發該類的初始化。④在使用類名獲取Class對象時不會觸發類的初始化。⑤在使用Class.forName()加載指定的類時,可以通過initialize參數設置是否需要初始化。⑥在使用ClassLoader默認的loadClass方法加載類時不會觸發該類的初始化。

Q5:哪些情況下類會初始化?
答:①創建類的實例。②訪問某個類或接口的靜態變量,或對該靜態變量賦值。③調用類的靜態方法。④初始化一個類的子類時(初始化子類,父類必須先初始化)。⑤JVM啓動時被標爲啓動類的類。⑥使用反射進行方法調用時。

Q6:談談JVM的運行時內存
答:JVM的運行時內存也叫做JVM堆,從GC角度更將其分爲新生代,老年代和永久代。
其中新生代默認佔1/3堆空間,老年代默認佔2/3堆空間,永久代佔非常少的堆空間。
新生代又分爲Eden區、ServivorFrom區和ServivorTo區,Eden區默認佔8/10新生代空間,ServivorFrom區和ServivorTo區默認分別佔1/10新生代空間。

Q7:談談新生代是怎麼分區的
答:①JVM新創建的對象(除了大對象外)會被存放在新生代,默認佔1/3堆內存空間。由於JVM會頻繁創建對象,所以新生代會頻繁觸發MinorGC進行垃圾回收。②新生代又分爲Eden區,ServivorFrom區和ServivorTo區。③Eden區:Java新創建的對象首先會被存放在Eden區,如果新創建的對象屬於大對象,則直接將其分配到老年代。大對象的定義和具體的JVM版本、堆大小和垃圾回收策略有關,一般爲2KB~128KB,可通過-XX:PretenureSizeThreshold設置其大小。在Eden區的內存空間不足時會觸發MinorGC,對新生代進行一次垃圾回收。②ServivorTo區:保留上一次MinorGC時的倖存者。③ServivorFrom區:將上一次MinorGC時的倖存者作爲這一次MinorGC的被掃描者。

Q8:談談新生代的垃圾回收機制
答:新生代的GC過程叫做MinorGC,採用複製算法實現,具體過程如下:
①把在Eden區和ServivorFrom區中存活的對象複製到ServivorTo區,如果某對象的年齡達到老年代的標準,則將其複製到老年代,同時把這些對象的年齡加1。如果ServivorTo區的內存空間不夠,則也直接將其複製到老年代。如果對象屬於大對象,則也直接複製到老年代。②清空Eden區和ServivorFrom區中的對象。③將ServivorFrom區和ServivorTo區互換,原來的ServivorTo區成爲下一次GC時的ServivorFrom區。

Q9:談談老年代的垃圾回收機制
答:①老年代主要存放有長生命週期的對象和大對象,老年代的GC叫MajorGC。②在老年代,對象比較穩定,MajorGC不會頻繁觸發。在進行MajorGC前,JVM會進行一次MinorGC,過後仍然出現老年代空間不足或無法找到足夠大的連續內存空間分配給新創建的大對象時,會觸發MajorGC進行垃圾回收,釋放JVM的內存空間。③MajorGC採用標記清除算法,該算法首先會掃描所有對象並標記存活的對象,然後回收未被標記的對象,並釋放內存空間。因爲要先掃描老年代的所有對象再回收,所以MajorGC的時間較長。容易產生內存碎片,在老年代沒有內存空間可分配時,會出現內存溢出異常。

Q10:談一談永久代
答:①永久代指內存的永久保存區域,主要存放Class和Meta(元數據)的信息。Class在類加載時被放入永久代。②永久代和老年代、新生代不同,GC不會在程序運行期間對永久代的內存進行清理,這也導致了永久代的內存會隨着加載的Class文件的增加而增加,在加載的Class文件過多時會出現內存溢出異常,比如Tomcat引用jar文件過多導致JVM內存不足而無法啓動。③在JDK1.8中,永久代已經被元數據區取代。元數據區的作用和永久代類似,二者最大的區別在於:元數據區並沒有使用虛擬機的內存,而是直接使用操作系統的本地內存。因此元空間的大小不受JVM內存的限制,只和操作系統的內存有關。④在JDK1.8中,JVM將類的元數據放入本地內存中,將常量池和類的靜態常量放入Java堆中,這樣JVM能夠加載多少元數據信息就不再由JVM的最大可用內存空間決定,而由操作系統的實際可用內存空間決定。

Q11:如何確定對象是否是垃圾?
答:①Java採用引用計數法可達性分析來確定對象是否應該被回收。引用計數法容易產生循環引用的問題,可達性分析通過根搜索算法實現。根搜索算法以一系列GC Roots的點作爲起點向下搜索,在一個對象到任何GC Roots都沒有引用鏈相連時,說明其已經死亡。根搜索算法主要針對棧中的引用、方法區的靜態引用和JNI中的引用展開分析。②引用計數法:在Java中如果要操作對象,就必須先獲取該對象的引用,因此可以通過引用計數法來判斷一個對象是否可以被回收。在爲對象添加一個引用時,引用計數加1;在爲對象刪除一個引用時,引用計數減1;如果一個對象的引用計數爲0,則表示此刻該對象沒有被引用,可以被回收。引用計數法容易產生循環引用問題,循環引用指兩個對象相互引用,導致它們的引用一直存在,而不能被回收。③可達性分析:爲了解決引用計數法的循環引用問題,Java還採用了可達性分析來判斷對象是否可以被回收。具哪些類加載器,類加載器的加載模型是什麼,有什麼好處?
答:①主要有啓動類加載器,負責加載JAVA_HOME/lib中的類庫;擴展類加載器,負責加載JAVA_HOME/lib/ext中的類庫;應用程序類加載器,也稱系統類加載器,負責加載用戶類路徑上指定的類庫;也可以自定義類加載器。②類加載器之間的層次關係叫做雙親委派模型,要求除了頂層的啓動類加載器外其餘的類加載器都應當有自己的父類加載器。一個類收到類加載請求後會層層找父類加載器去嘗試加載,因此所有的加載請求最終都會被傳送到頂層的啓動類加載器,只有當父類加載器反饋自己無法完成加載時子加載器纔會嘗試自己去加載。③雙親委派模型的好處是保障類加載的唯一性和安全性,例如加載rt.jar包中的java.lang.Object,無論哪一個類加載最終都會委託給啓動類加載器,這樣就保證了類加載的唯一性。如果存在包名和類名都相同的兩個類,那麼該類就無法被加載。

Q3:簡述JVM的內存區域
答:JVM的內存區域分爲線程私有區域(程序計數器、虛擬機棧、本地方法區)、線程共享區域(堆、方法區)和直接內存。①程序計數器是一塊很小的內存空間,用於存儲當前線程執行字節碼文件的行號指示器。②虛擬機棧是描述Java方法執行過程的內存模型,幀棧中存儲了局部變量表,操作數棧,動態鏈接,方法出口等信息。③本地方法棧,和虛擬機棧作用類似,區別是虛擬機棧爲Java方法服務,本地方法棧爲Native方法服務。④JVM運行過程中創建的對象和生成的數據都存儲在堆中,堆是被線程共享的內存區域,也是垃圾回收最主要的內存區域。⑤方法區用來存儲常量,靜態變量、類信息、即時編譯器編譯後的機器碼、運行時常量池等數據。

Q4:哪些情況下類不會初始化?
答:①常量在編譯時會存放在使用該常量的類的常量池,該過程不要調用常量所在的類,不會初始化。②子類引用父類的靜態變量時,子類不會初始化,只有父類會初始化。③定義對象數組,不會觸發該類的初始化。④在使用類名獲取Class對象時不會觸發類的初始化。⑤在使用Class.forName()加載指定的類時,可以通過initialize參數設置是否需要初始化。⑥在使用ClassLoader默認的loadClass方法加載類時不會觸發該類的初始化。

Q5:哪些情況下類會初始化?
答:①創建類的實例。②訪問某個類或接口的靜態變量,或對該靜態變量賦值。③調用類的靜態方法。④初始化一個類的子類時(初始化子類,父類必須先初始化)。⑤JVM啓動時被標爲啓動類的類。⑥使用反射進行方法調用時。

Q6:談談JVM的運行時內存
答:JVM的運行時內存也叫做JVM堆,從GC角度更將其分爲新生代,老年代和永久代。
其中新生代默認佔1/3堆空間,老年代默認佔2/3堆空間,永久代佔非常少的堆空間。
新生代又分爲Eden區、ServivorFrom區和ServivorTo區,Eden區默認佔8/10新生代空間,ServivorFrom區和ServivorTo區默認分別佔1/10新生代空間。

Q7:談談新生代是怎麼分區的
答:①JVM新創建的對象(除了大對象外)會被存放在新生代,默認佔1/3堆內存空間。由於JVM會頻繁創建對象,所以新生代會頻繁觸發MinorGC進行垃圾回收。②新生代又分爲Eden區,ServivorFrom區和ServivorTo區。③Eden區:Java新創建的對象首先會被存放在Eden區,如果新創建的對象屬於大對象,則直接將其分配到老年代。大對象的定義和具體的JVM版本、堆大小和垃圾回收策略有關,一般爲2KB~128KB,可通過-XX:PretenureSizeThreshold設置其大小。在Eden區的內存空間不足時會觸發MinorGC,對新生代進行一次垃圾回收。②ServivorTo區:保留上一次MinorGC時的倖存者。③ServivorFrom區:將上一次MinorGC時的倖存者作爲這一次MinorGC的被掃描者。

Q8:談談新生代的垃圾回收機制
答:新生代的GC過程叫做MinorGC,採用複製算法實現,具體過程如下:
①把在Eden區和ServivorFrom區中存活的對象複製到ServivorTo區,如果某對象的年齡達到老年代的標準,則將其複製到老年代,同時把這些對象的年齡加1。如果ServivorTo區的內存空間不夠,則也直接將其複製到老年代。如果對象屬於大對象,則也直接複製到老年代。②清空Eden區和ServivorFrom區中的對象。③將ServivorFrom區和ServivorTo區互換,原來的ServivorTo區成爲下一次GC時的ServivorFrom區。

Q9:談談老年代的垃圾回收機制
答:①老年代主要存放有長生命週期的對象和大對象,老年代的GC叫MajorGC。②在老年代,對象比較穩定,MajorGC不會頻繁觸發。在進行MajorGC前,JVM會進行一次MinorGC,過後仍然出現老年代空間不足或無法找到足夠大的連續內存空間分配給新創建的大對象時,會觸發MajorGC進行垃圾回收,釋放JVM的內存空間。③MajorGC採用標記清除算法,該算法首先會掃描所有對象並標記存活的對象,然後回收未被標記的對象,並釋放內存空間。因爲要先掃描老年代的所有對象再回收,所以MajorGC的時間較長。容易產生內存碎片,在老年代沒有內存空間可分配時,會出現內存溢出異常。

Q10:談一談永久代
答:①永久代指內存的永久保存區域,主要存放Class和Meta(元數據)的信息。Class在類加載時被放入永久代。②永久代和老年代、新生代不同,GC不會在程序運行期間對永久代的內存進行清理,這也導致了永久代的內存會隨着加載的Class文件的增加而增加,在加載的Class文件過多時會出現內存溢出異常,比如Tomcat引用jar文件過多導致JVM內存不足而無法啓動。③在JDK1.8中,永久代已經被元數據區取代。元數據區的作用和永久代類似,二者最大的區別在於:元數據區並沒有使用虛擬機的內存,而是直接使用操作系統的本地內存。因此元空間的大小不受JVM內存的限制,只和操作系統的內存有關。④在JDK1.8中,JVM將類的元數據放入本地內存中,將常量池和類的靜態常量放入Java堆中,這樣JVM能夠加載多少元數據信息就不再由JVM的最大可用內存空間決定,而由操作系統的實際可用內存空間決定。

Q11:如何確定對象是否是垃圾?
答:①Java採用引用計數法可達性分析來確定對象是否應該被回收。引用計數法容易產生循環引用的問題,可達性分析通過根搜索算法實現。根搜索算法以一系列GC Roots的點作爲起點向下搜索,在一個對象到任何GC Roots都沒有引用鏈相連時,說明其已經死亡。根搜索算法主要針對棧中的引用、方法區的靜態引用和JNI中的引用展開分析。②引用計數法:在Java中如果要操作對象,就必須先獲取該對象的引用,因此可以通過引用計數法來判斷一個對象是否可以被回收。在爲對象添加一個引用時,引用計數加1;在爲對象刪除一個引用時,引用計數減1;如果一個對象的引用計數爲0,則表示此刻該對象沒有被引用,可以被回收。引用計數法容易產生循環引用問題,循環引用指兩個對象相互引用,導致它們的引用一直存在,而不能被回收。③可達性分析:爲了解決引用計數法的循環引用問題,Java還採用了可達性分析來判斷對象是否可以被回收。具體做法是首先定義一些GC Roots對象,然後以這些GC Roots對象作爲起點向下搜索,如果在GC Roots和一個對象之間沒有可達路徑,則稱該對象是不可達的。不可達對象要經過至少兩次標記才能判斷其是否可被回收,如果兩次標記後該對象仍然不可達,則將被垃圾回收器回收。

Q12:有哪些GC算法?分別有什麼特點? 答:①標記清除算法:標記出所有需要回收的對象,然後清除可回收的對象。效率較低,並且因爲在清除後沒有重新整理可用的內存空間,如果內存中可被回收的小對象居多,會引起內存碎片化問題。②複製算法:將可用內存分爲區域1和區域2,將新生成的對象放在區域1,在區域1滿後對區域1進行一次標記,將標記後仍然存活的對象複製到區域2,然後清除區域1。效率較高並且易於實現,解決了內存碎片化的問題,缺點是浪費了大量內存,同時在系統中存在長生命週期對象時會在兩區域間來回複製影響系統效率。③標記清除算法:結合了標記清除算法和複製算法的優點,標記過程和標記清除算法一樣,標記後將存活的對象移動到一端,清理另一端。④分代收集算法:根據對象不同類型把內存劃分爲不同區域,把堆劃分爲新生代和老年代。由於新生代的對象生命週期較短,主要採用複製算法。將新生代劃分爲一塊較大的Eden區和兩塊較小的Survivor區,Servivor區又分爲ServivorTo和ServivorFrom區。J

相關文章
相關標籤/搜索