1. 兩個物件的hashCode相同,則equals也一定為true,對嗎?
不對,答案見下面的代碼:
@Override
public int hashCode() {
return 1;
}
兩個物件equals為true,則hashCode也一定相同,對嗎?
這塊肯定是有爭議的,面試的時候這樣答:如果按照官方設計要求來打代碼的話,hashcode一定相等,但是如果不按官方照設計要求、不重寫hashcode方法,就會出現不相等的情況,
2. java執行緒池用過沒有?
Executors提供了四種方法來創建執行緒池,
- newFixedThreadPool() :創建固定大小的執行緒池,
- newCachedThreadPool(): 創建無限大小的執行緒池,執行緒池中執行緒數量不固定,可根據需求自動更改,
- newSingleThreadPool() : 創建單個執行緒池,執行緒池中只有一個執行緒,
- newScheduledThreadPool() 創建固定大小的執行緒池,可以延遲或定時的執行任務,
手寫一個:
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.execute(() -> {
for (int i = 0; i< 20;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
});
threadPool.shutdown();
}
執行緒池作用
- 限制執行緒個數,避免執行緒過多導致系統運行緩慢或崩潰,
- 不需要頻繁的創建和銷毀,節約資源、回應更快,
大家覺得本次面試題總結的寫得不錯的朋友,大家可以轉發+關注,然后掃描下方二維碼獲取更多面試題以及答案— 掃描添加暗號:【CSDN】
3. Math.round(-2.5)等于多少?
不要認為它是四舍五入!不要認為它是四舍五入!不要認為它是四舍五入!
口訣:+0.5后向下取整,所以結果是-2,
留個題,Math.round(-2.6)結果和Math.round(2.6)結果
4. 面向物件六大原則
- 單一職責原則——SRP
讓每個類只專心處理自己的方法,
- 開閉原則——OCP
軟體中的物件(類,模塊,函式等)應該對于擴展是開放的,但是對于修改是關閉的,
- 里式替換原則——LSP
子類可以去擴展父類,但是不能改變父類原有的功能,
- 依賴倒置原則——DIP
應該通過呼叫介面或抽象類(比較高層),而不是呼叫實作類(細節),
- 介面隔離原則——ISP
把介面分成滿足依賴關系的最小介面,實作類中不能有不需要的方法,
- 迪米特原則——LOD
高內聚,低耦合,
5. static和final區別
| 關鍵詞 | 修飾物 | 影響 |
|---|---|---|
| final | 變數 | 分配到常量池中,程式不可改變其值 |
| final | 方法 | 子類中將不能被重寫 |
| final | 類 | 不能被繼承 |
| static | 變數 | 分配在記憶體堆上,參考都會指向這一個地址而不會重新分配記憶體 |
| static | 方法塊 | 虛擬機優先加載 |
| static | 類 | 可以直接通過類來呼叫而不需要new |
6. String s = "hello"和String s = new String("hello");區別
String s = new String("hello");可能創建兩個物件也可能創建一個物件,如果常量池中有hello字串常量的話,則僅僅在堆中創建一個物件,如果常量池中沒有hello物件,則堆上和常量池都需要創建,
String s = "hello"這樣創建的物件,JVM會直接檢查字串常量池是否已有"hello"字串物件,如沒有,就分配一個記憶體存放"hello",如有了,則直接將字串常量池中的地址回傳給堆疊,(沒有new,沒有堆的操作)
7. 參考型別是占用幾個位元組?
hotspot在64位平臺上,占8個位元組,在32位平臺上占4個位元組,
8. `(1<3)?"a":"b")+3+4`和`(1<3)?"a":"b")+(3+4)`區別
System.out.println(((1<3)?"a":"b")+3+4);
System.out.println(((1<3)?"a":"b")+(3+4));
控制臺:
a34
a7
8.1 什么情況下,加號會變成字串連接符
依據上面的例子來思考,
9. java中的switch選擇結構可以使用資料型別的資料(JDK1.8)

- char
- byte
- short
- int
- Character
- Byte
- Short
- Integer
- String
- enum
更好的記憶方法:
基本型別中,沒有boolean和浮點型別+長型別long.相應的包裝型別也沒有,
外加String和enum,
10. `4&5``4^5``4&10>>1`各等于多少
// 0100 & 0101 = 0100 = 4
System.out.println(4&5);
// 0100 ^ 0101 = 0001 = 1
System.out.println(4^5);
System.out.println(10>>1);
// 有疑問參考下面的運算子優先級
System.out.println(4&10>>1);
4
1
5
4
`4|5`等于多少呢
答案:5
運算子優先級
| 運算子 | 結合性 |
|---|---|
[ ] . ( ) (方法呼叫) | 從左向右 |
! ~ ++ -- +(一元運算) -(一元運算) | 從右向左 |
* / % | 從左向右 |
+ - | 從左向右 |
<< >> >>> | 從左向右 |
< <= > >= instanceof | 從左向右 |
== != | 從左向右 |
& | 從左向右 |
^ | 從左向右 |
| | 從左向右 |
&& | 從左向右 |
|| | 從左向右 |
?: | 從右向左 |
= | 從右向左 |
11. 某些java類為什么要實作Serializable介面
為了網路進行傳輸或者持久化
什么是序列化
將物件的狀態資訊轉換為可以存盤或傳輸的形式的程序
除了實作Serializable介面還有什么序列化方式
- Json序列化
- FastJson序列化
- ProtoBuff序列化
…
12. JVM垃圾處理方法
標記-清除演算法(老年代)
該演算法分為“標記”和“清除”兩個階段: 首先標記出所有需要回收的物件(可達性分析), 在標記完成后統一清理掉所有被標記的物件.

該演算法會有兩個問題:
- 效率問題,標記和清除效率不高,
- 空間問題: 標記清除后會產生大量不連續的記憶體碎片, 空間碎片太多可能會導致在運行程序中需要分配較大物件時無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集,
所以它一般用于"垃圾不太多的區域,比如老年代",
復制演算法(新生代)
該演算法的核心是將可用記憶體按容量劃分為大小相等的兩塊, 每次只用其中一塊, 當這一塊的記憶體用完, 就將還存活的物件(非垃圾)復制到另外一塊上面, 然后把已使用過的記憶體空間一次清理掉,
優點:不用考慮碎片問題,方法簡單高效,
缺點:記憶體浪費嚴重,
現代商用VM的新生代均采用復制演算法,但由于新生代中的98%的物件都是生存周期極短的,因此并不需完全按照1∶1的比例劃分新生代空間,而是將新生代劃分為一塊較大的Eden區和兩塊較小的Survivor區(HotSpot默認Eden和Survivor的大小比例為8∶1), 每次只用Eden和其中一塊Survivor,
當發生MinorGC時,將Eden和Survivor中還存活著的物件一次性地拷貝到另外一塊Survivor上, 最后清理掉Eden和剛才用過的Survivor的空間,當Survivor空間不夠用(不足以保存尚存活的物件)時,需要依賴老年代進行空間分配擔保機制,這部分記憶體直接進入老年代,

復制演算法的空間分配擔保:
在執行Minor GC前, VM會首先檢查老年代是否有足夠的空間存放新生代尚存活物件, 由于新生代使用復制收集演算法, 為了提升記憶體利用率, 只使用了其中一個Survivor作為輪換備份, 因此當出現大量物件在Minor GC后仍然存活的情況時, 就需要老年代進行分配擔保, 讓Survivor無法容納的物件直接進入老年代, 但前提是老年代需要有足夠的空間容納這些存活物件.
但存活物件的大小在實際完成GC前是無法明確知道的, 因此Minor GC前, VM會先首先檢查老年代連續空間是否大于新生代物件總大小或歷次晉升的平均大小, 如果條件成立, 則進行Minor GC, 否則進行Full GC(讓老年代騰出更多空間).
然而取歷次晉升的物件的平均大小也是有一定風險的, 如果某次Minor GC存活后的物件突增,遠遠高于平均值的話,依然可能導致擔保失敗(Handle Promotion Failure, 老年代也無法存放這些物件了), 此時就只好在失敗后重新發起一次Full GC(讓老年代騰出更多空間).
標記-整理演算法(老年代)
標記清除演算法會產生記憶體碎片問題, 而復制演算法需要有額外的記憶體擔保空間, 于是針對老年代的特點, 又有了標記整理演算法. 標記整理演算法的標記程序與標記清除演算法相同, 但后續步驟不再對可回收物件直接清理, 而是讓所有存活的物件都向一端移動,然后清理掉端邊界以外的記憶體.

13. 新生代、老年代、持久代都存盤哪些東西
新生代:
- 方法中new一個物件,就會先進入新生代,
老年代:
- 新生代中經歷了N次垃圾回收仍然存活的物件就會被放到老年代中,
- 大物件一般直接放入老年代,
- 當Survivor空間不足,需要老年代擔保一些空間,也會將物件放入老年代,
永久代:
指的就是方法區,
14. 可達性演算法中,哪些物件可作為GC Roots物件,
- 虛擬機堆疊中參考的物件
- 方法區靜態成員參考的物件
- 方法區常量參考物件
- 本地方法堆疊JNI參考的物件
15. 什么時候進行MinGC和FullGC
MinGC:
- 當Eden區滿時,觸發Minor GC.
FullGC:
- 呼叫System.gc時,系統建議執行Full GC,但是不必然執行
- 老年代空間不足
- 方法區空間不足
- 通過Minor GC后進入老年代的平均大小大于老年代的剩余空間
- 堆中分配很大的物件,而老年代沒有足夠的空間
大家覺得本次面試題總結的寫得不錯的朋友,大家可以轉發+關注,然后掃描下方二維碼獲取更多面試題以及答案— 掃描添加暗號:【CSDN】
16. 如何判定物件為垃圾物件
在堆里面存放著Java世界中幾乎所有的物件實體, 垃圾收集器在對堆進行回收前, 第一件事就是判斷哪些物件已死(可回收).
參考計數法
在JDK1.2之前,使用的是參考計數器演算法,
在物件中添加一個參考計數器,當有地方參考這個物件的時候,參考計數器的值就+1,當參考失效的時候,計數器的值就-1,當參考計數器被減為零的時候,標志著這個物件已經沒有參考了,可以回收了!

問題:如果在A類中呼叫B類的方法,B類中呼叫A類的方法,這樣當其他所有的參考都消失了之后,A和B還有一個相互的參考,也就是說兩個物件的參考計數器各為1,而實際上這兩個物件都已經沒有額外的參考,已經是垃圾了,但是該演算法并不會計算出該型別的垃圾,
可達性分析法
在主流商用語言(如Java、C#)的主流實作中, 都是通過可達性分析演算法來判定物件是否存活的: 通過一系列的稱為 GC Roots 的物件作為起點, 然后向下搜索; 搜索所走過的路徑稱為參考鏈/Reference Chain, 當一個物件到 GC Roots 沒有任何參考鏈相連時, 即該物件不可達, 也就說明此物件是不可用的, 如下圖:雖然E和F相互關聯, 但它們到GC Roots是不可達的, 因此也會被判定為可回收的物件,

注: 即使在可達性分析演算法中不可達的物件, VM也并不是馬上對其回收, 因為要真正宣告一個物件死亡, 至少要經歷兩次標記程序: 第一次是在可達性分析后發現沒有與GC Roots相連接的參考鏈, 第二次是GC對在F-Queue執行佇列中的物件進行的小規模標記(物件需要覆寫finalize()方法且沒被呼叫過).
17. 你能說出來幾個垃圾收集器
Serial
Serial收集器是Hotspot運行在Client模式下的默認新生代收集器, 它在進行垃圾收集時,會暫停所有的作業行程,用一個執行緒去完成GC作業

特點:簡單高效,適合jvm管理記憶體不大的情況(十兆到百兆),
Parnew
ParNew收集器其實是Serial的多執行緒版本,回收策略完全一樣,但是他們又有著不同,

我們說了Parnew是多執行緒gc收集,所以它配合多核心的cpu效果更好,如果是一個cpu,他倆效果就差不多,(可用-XX:ParallelGCThreads引數控制GC執行緒數)
Cms
CMS(Concurrent Mark Sweep)收集器是一款具有劃時代意義的收集器, 一款真正意義上的并發收集器, 雖然現在已經有了理論意義上表現更好的G1收集器, 但現在主流互聯網企業線上選用的仍是CMS(如Taobao),又稱多并發低暫停的收集器,

由他的英文組成可以看出,它是基于標記-清除演算法實作的,整個程序分4個步驟:
- 初始標記(CMS initial mark):僅只標記一下GC Roots能直接關聯到的物件, 速度很快
- 并發標記(CMS concurrent mark: GC Roots Tracing程序)
- 重新標記(CMS remark):修正并發標記期間因用戶程式繼續運行而導致標記產生變動的那一部分物件的標記記錄
- 并發清除(CMS concurrent sweep: 已死物件將會就地釋放)
可以看到,初始標記、重新標記需要STW(stop the world 即:掛起用戶執行緒)操作,因為最耗時的操作是并發標記和并發清除,所以總體上我們認為CMS的GC與用戶執行緒是并發運行的,
優點:并發收集、低停頓
缺點:
- CMS默認啟動的回收執行緒數=(CPU數目+3)*4
當CPU數>4時, GC執行緒最多占用不超過25%的CPU資源, 但是當CPU數<=4時, GC執行緒可能就會過多的占用用戶CPU資源, 從而導致應用程式變慢, 總吞吐量降低. - 無法清除浮動垃圾(GC運行到并發清除階段時用戶執行緒產生的垃圾),因為用戶執行緒是需要記憶體的,如果浮動垃圾施放不及時,很可能就造成記憶體溢位,所以CMS不能像別的垃圾收集器那樣等老年代幾乎滿了才觸發,CMS提供了引數
-XX:CMSInitiatingOccupancyFraction來設定GC觸發百分比(1.6后默認92%),當然我們還得設定啟用該策略-XX:+UseCMSInitiatingOccupancyOnly - 因為CMS采用標記-清除演算法,所以可能會帶來很多的碎片,如果碎片太多沒有清理,jvm會因為無法分配大物件記憶體而觸發GC,因此CMS提供了
-XX:+UseCMSCompactAtFullCollection引數,它會在GC執行完后接著進行碎片整理,但是又會有個問題,碎片整理不能并發,所以必須單執行緒去處理,所以如果每次GC完都整理用戶執行緒stop的時間累積會很長,所以XX:CMSFullGCsBeforeCompaction引數設定隔幾次GC進行一次碎片整理(默認為0),
G1
同優秀的CMS垃圾回收器一樣,G1也是關注最小時延的垃圾回收器,也同樣適合大尺寸堆記憶體的垃圾收集,官方也推薦使用G1來代替選擇CMS,G1最大的特點是引入磁區的思路,榷訓分代的概念,合理利用垃圾收集各個周期的資源,解決了其他收集器甚至CMS的眾多缺陷,

因為每個區都有E、S、O代,所以在G1中,不需要對整個Eden等代進行回收,而是尋找可回收物件比較多的區,然后進行回收(雖然也需要STW操作,但是花費的時間是很少的),保證高效率,
新生代收集
G1的新生代收集跟ParNew類似,如果存活時間超過某個閾值,就會被轉移到S/O區,
年輕代記憶體由一組不連續的heap區組成, 這種方法使得可以動態調整各代區域的大小
老年代收集
分為以下幾個階段:
- 初始標記 (Initial Mark: Stop the World Event)
在G1中, 該操作附著一次年輕代GC, 以標記Survivor中有可能參考到老年代物件的Regions. - 掃描根區域 (Root Region Scanning: 與應用程式并發執行)
掃描Survivor中能夠參考到老年代的references. 但必須在Minor GC觸發前執行完 - 并發標記 (Concurrent Marking : 與應用程式并發執行)
在整個堆中查找存活物件, 但該階段可能會被Minor GC中斷 - 重新標記 (Remark : Stop the World Event)
完成堆記憶體中存活物件的標記. 使用snapshot-at-the-beginning(SATB, 起始快照)演算法, 比CMS所用演算法要快得多(空Region直接被移除并回收, 并計算所有區域的活躍度). - 清理 (Cleanup : Stop the World Event and Concurrent)
在含有存活物件和完全空閑的區域上進行統計(STW)、擦除Remembered Sets(使用Remembered Set來避免掃描全堆,每個區都有對應一個Set用來記錄參考資訊、讀寫操作記錄)(STW)、重置空regions并將他們返還給空閑串列(free list)(Concurrent)
詳情請看參考檔案
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html#t5
18. JVM中物件的創建程序
1. 拿到記憶體創建指令
當虛擬機遇到記憶體創建的指令的時候(new 類名),來到了方法區,找 根據new的引數在常量池中定位一個類的符號參考,
2. 檢查符號參考
檢查該符號參考有沒有被加載、決議和初始化過,如果沒有則執行類加載程序,否則直接準備為新的物件分配記憶體
3. 分配記憶體
虛擬機為物件分配記憶體(堆)分配記憶體分為指標碰撞和空閑串列兩種方式;分配記憶體還要要保證并發安全,有兩種方式,
3.1. 指標碰撞
所有的存盤空間分為兩部分,一部分是空閑,一部分是占用,需要分配空間的時候,只需要計算指標移動的長度即可,
3.2. 空閑串列
虛擬機維護了一個空閑串列,需要分配空間的時候去查該空閑串列進行分配并對空閑串列做更新,
可以看出,記憶體分配方式是由java堆是否規整決定的,java堆的規整是由垃圾回識訓制來決定的
3.2.5 安全性問題的思考
假如分配記憶體策略是指標碰撞,如果在高并發情況下,多個物件需要分配記憶體,如果不做處理,肯定會出現執行緒安全問題,導致一些物件分配不到空間等,
下面是解決方案:
3.3 執行緒同步策略
也就是每個執行緒都進行同步,防止出現執行緒安全,
3.4. 本地執行緒分配緩沖
也稱TLAB(Thread Local Allocation Buffer),在堆中為每一個執行緒分配一小塊獨立的記憶體,這樣以來就不存并發問題了,Java 層面與之對應的是 ThreadLocal 類的實作
4. 初始化
- 分配完記憶體后要對物件的頭(Object Header)進行初始化,這新資訊包括:該物件對應類的元資料、該物件的GC代、物件的哈希碼,
- 抽象資料型別默認初始化為null,基本資料型別為0,布爾為false....
5. 呼叫物件的初始化方法
也就是執行構造方法,
以上便是此次分享的面試題以及答案,如果覺得還不過癮,大家可以關注我的公眾號-【Java爛豬皮】,里面有往期的面試題以及最新的面試分享,關注后回復:【666】即可免費獲取更多的Java架構進階vip學習資料

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/188051.html
標籤:其他

