Java崗 面試考點精講(基礎篇02期)


1. 兩個對象的hashCode相同,則equals也一定為true,對嗎?

不對,答案見下面的代碼:

1 @Override
2 public int hashCode() {
3     return 1;
4 }

 

兩個對象equals為true,則hashCode也一定相同,對嗎?

這塊肯定是有爭議的。面試的時候這樣答:如果按照官方設計要求來打代碼的話,hashcode一定相等。但是如果不按官方照設計要求、不重寫hashcode方法,就會出現不相等的情況。

2. java線程池用過沒有?

Executors提供了四種方法來創建線程池。

 

  1. newFixedThreadPool() :創建固定大小的線程池。
  2. newCachedThreadPool(): 創建無限大小的線程池,線程池中線程數量不固定,可根據需求自動更改。
  3. newSingleThreadPool() : 創建單個線程池,線程池中只有一個線程。
  4. newScheduledThreadPool() 創建固定大小的線程池,可以延遲或定時的執行任務。

手寫一個:

 1 public static void main(String[] args) {
 2 
 3     ExecutorService threadPool = Executors.newCachedThreadPool();
 4     threadPool.execute(() -> {
 5         for (int i = 0; i< 20;i++) {
 6             System.out.println(Thread.currentThread().getName()+":"+i);
 7         }
 8     });
 9     threadPool.shutdown();
10 }

 

線程池作用
  1. 限制線程個數,避免線程過多導致系統運行緩慢或崩潰。
  2. 不需要頻繁的創建和銷毀,節約資源、響應更快。

3. Math.round(-2.5)等於多少?

不要認為它是四舍五入!不要認為它是四舍五入!不要認為它是四舍五入!

 

口訣:+0.5后向下取整。所以結果是-2。

留個題,Math.round(-2.6)結果和Math.round(2.6)結果

4. 面向對象六大原則

  1. 單一職責原則——SRP

讓每個類只專心處理自己的方法。

 

  1. 開閉原則——OCP

軟件中的對象(類,模塊,函數等)應該對於擴展是開放的,但是對於修改是關閉的。

 

  1. 里式替換原則——LSP

子類可以去擴展父類,但是不能改變父類原有的功能。

 

  1. 依賴倒置原則——DIP

應該通過調用接口或抽象類(比較高層),而不是調用實現類(細節)。

 

  1. 接口隔離原則——ISP

把接口分成滿足依賴關系的最小接口,實現類中不能有不需要的方法。

 

  1. 迪米特原則——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)`區別

1 System.out.println(((1<3)?"a":"b")+3+4);
2 System.out.println(((1<3)?"a":"b")+(3+4));

 

控制台:

a34
a7

8.1 什么情況下,加號會變成字符串連接符

依據上面的例子來思考。

9. java中的switch選擇結構可以使用數據類型的數據(JDK1.8)

Java崗 面試考點精講(基礎篇02期)

 

  1. char
  2. byte
  3. short
  4. int
  5. Character
  6. Byte
  7. Short
  8. Integer
  9. String
  10. enum

更好的記憶方法:

基本類型中,沒有boolean浮點類型+長類型long.相應的包裝類型也沒有。

外加Stringenum

10. `4&5``4^5``4&10>>1`各等於多少

1 // 0100 & 0101 = 0100 = 4
2 System.out.println(4&5);
3 // 0100 ^ 0101 = 0001 = 1
4 System.out.println(4^5);
5 System.out.println(10>>1);
6  // 有疑問參考下面的運算符優先級
7 System.out.println(4&10>>1);

 

4
1
5
4

`4|5`等於多少呢

答案:5

運算符優先級
運算符 結合性
[ ] . ( ) (方法調用) 從左向右
! ~ ++ -- +(一元運算) -(一元運算) 從右向左
* / % 從左向右
+ - 從左向右
<< >> >>> 從左向右
< <= > >= instanceof 從左向右
== != 從左向右
& 從左向右
^ 從左向右
| 從左向右
&& 從左向右
|| 從左向右
?: 從右向左
= 從右向左

11. 某些java類為什么要實現Serializable接口

為了網絡進行傳輸或者持久化

什么是序列化

將對象的狀態信息轉換為可以存儲或傳輸的形式的過程

除了實現Serializable接口還有什么序列化方式
  • Json序列化
  • FastJson序列化
  • ProtoBuff序列化

12. JVM垃圾處理方法

標記-清除算法(老年代)

該算法分為“標記”和“清除”兩個階段: 首先標記出所有需要回收的對象(可達性分析), 在標記完成后統一清理掉所有被標記的對象.

Java崗 面試考點精講(基礎篇02期)

該算法會有兩個問題:

  1. 效率問題,標記和清除效率不高。
  2. 空間問題: 標記清除后會產生大量不連續的內存碎片, 空間碎片太多可能會導致在運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集。

所以它一般用於"垃圾不太多的區域,比如老年代"。

復制算法(新生代)

該算法的核心是將可用內存按容量划分為大小相等的兩塊, 每次只用其中一塊, 當這一塊的內存用完, 就將還存活的對象(非垃圾)復制到另外一塊上面, 然后把已使用過的內存空間一次清理掉。

優點:不用考慮碎片問題,方法簡單高效。

缺點:內存浪費嚴重。

現代商用VM的新生代均采用復制算法,但由於新生代中的98%的對象都是生存周期極短的,因此並不需完全按照1∶1的比例划分新生代空間,而是將新生代划分為一塊較大的Eden區和兩塊較小的Survivor區(HotSpot默認Eden和Survivor的大小比例為8∶1), 每次只用Eden和其中一塊Survivor。

當發生MinorGC時,將Eden和Survivor中還存活着的對象一次性地拷貝到另外一塊Survivor上, 最后清理掉Eden和剛才用過的Survivor的空間。當Survivor空間不夠用(不足以保存尚存活的對象)時,需要依賴老年代進行空間分配擔保機制,這部分內存直接進入老年代。

Java崗 面試考點精講(基礎篇02期)

復制算法的空間分配擔保:
在執行Minor GC前, VM會首先檢查老年代是否有足夠的空間存放新生代尚存活對象, 由於新生代使用復制收集算法, 為了提升內存利用率, 只使用了其中一個Survivor作為輪換備份, 因此當出現大量對象在Minor GC后仍然存活的情況時, 就需要老年代進行分配擔保, 讓Survivor無法容納的對象直接進入老年代, 但前提是老年代需要有足夠的空間容納這些存活對象.

但存活對象的大小在實際完成GC前是無法明確知道的, 因此Minor GC前, VM會先首先檢查老年代連續空間是否大於新生代對象總大小或歷次晉升的平均大小, 如果條件成立, 則進行Minor GC, 否則進行Full GC(讓老年代騰出更多空間).

然而取歷次晉升的對象的平均大小也是有一定風險的, 如果某次Minor GC存活后的對象突增,遠遠高於平均值的話,依然可能導致擔保失敗(Handle Promotion Failure, 老年代也無法存放這些對象了), 此時就只好在失敗后重新發起一次Full GC(讓老年代騰出更多空間).


標記-整理算法(老年代)

標記清除算法會產生內存碎片問題, 而復制算法需要有額外的內存擔保空間, 於是針對老年代的特點, 又有了標記整理算法. 標記整理算法的標記過程與標記清除算法相同, 但后續步驟不再對可回收對象直接清理, 而是讓所有存活的對象都向一端移動,然后清理掉端邊界以外的內存.

Java崗 面試考點精講(基礎篇02期)

13. 新生代、老年代、持久代都存儲哪些東西

新生代:

  1. 方法中new一個對象,就會先進入新生代。

老年代:

  1. 新生代中經歷了N次垃圾回收仍然存活的對象就會被放到老年代中。
  2. 大對象一般直接放入老年代。
  3. 當Survivor空間不足。需要老年代擔保一些空間,也會將對象放入老年代。

永久代:

指的就是方法區。

14. 可達性算法中,哪些對象可作為GC Roots對象。

  1. 虛擬機棧中引用的對象
  2. 方法區靜態成員引用的對象
  3. 方法區常量引用對象
  4. 本地方法棧JNI引用的對象

15. 什么時候進行MinGC和FullGC

MinGC:

  1. 當Eden區滿時,觸發Minor GC.

FullGC:

  1. 調用System.gc時,系統建議執行Full GC,但是不必然執行
  2. 老年代空間不足
  3. 方法區空間不足
  4. 通過Minor GC后進入老年代的平均大小大於老年代的剩余空間
  5. 堆中分配很大的對象,而老年代沒有足夠的空間

16. 如何判定對象為垃圾對象

在堆里面存放着Java世界中幾乎所有的對象實例, 垃圾收集器在對堆進行回收前, 第一件事就是判斷哪些對象已死(可回收).

引用計數法

在JDK1.2之前,使用的是引用計數器算法。

在對象中添加一個引用計數器,當有地方引用這個對象的時候,引用計數器的值就+1,當引用失效的時候,計數器的值就-1,當引用計數器被減為零的時候,標志着這個對象已經沒有引用了,可以回收了!

Java崗 面試考點精講(基礎篇02期)


問題:如果在A類中調用B類的方法,B類中調用A類的方法,這樣當其他所有的引用都消失了之后,A和B還有一個相互的引用,也就是說兩個對象的引用計數器各為1,而實際上這兩個對象都已經沒有額外的引用,已經是垃圾了。但是該算法並不會計算出該類型的垃圾。

可達性分析法

在主流商用語言(如Java、C#)的主流實現中, 都是通過可達性分析算法來判定對象是否存活的: 通過一系列的稱為 GC Roots 的對象作為起點, 然后向下搜索; 搜索所走過的路徑稱為引用鏈/Reference Chain, 當一個對象到 GC Roots 沒有任何引用鏈相連時, 即該對象不可達, 也就說明此對象是不可用的, 如下圖:雖然E和F相互關聯, 但它們到GC Roots是不可達的, 因此也會被判定為可回收的對象。

Java崗 面試考點精講(基礎篇02期)

注: 即使在可達性分析算法中不可達的對象, VM也並不是馬上對其回收, 因為要真正宣告一個對象死亡, 至少要經歷兩次標記過程: 第一次是在可達性分析后發現沒有與GC Roots相連接的引用鏈, 第二次是GC對在F-Queue執行隊列中的對象進行的小規模標記(對象需要覆蓋finalize()方法且沒被調用過).

17. 你能說出來幾個垃圾收集器

Serial

Serial收集器是Hotspot運行在Client模式下的默認新生代收集器, 它在進行垃圾收集時,會暫停所有的工作進程,用一個線程去完成GC工作

Java崗 面試考點精講(基礎篇02期)

特點:簡單高效,適合jvm管理內存不大的情況(十兆到百兆)。

Parnew

ParNew收集器其實是Serial的多線程版本,回收策略完全一樣,但是他們又有着不同。

Java崗 面試考點精講(基礎篇02期)

我們說了Parnew是多線程gc收集,所以它配合多核心的cpu效果更好,如果是一個cpu,他倆效果就差不多。(可用-XX:ParallelGCThreads參數控制GC線程數)

Cms

CMS(Concurrent Mark Sweep)收集器是一款具有划時代意義的收集器, 一款真正意義上的並發收集器, 雖然現在已經有了理論意義上表現更好的G1收集器, 但現在主流互聯網企業線上選用的仍是CMS(如Taobao),又稱多並發低暫停的收集器。

Java崗 面試考點精講(基礎篇02期)

由他的英文組成可以看出,它是基於標記-清除算法實現的。整個過程分4個步驟:

  1. 初始標記(CMS initial mark):僅只標記一下GC Roots能直接關聯到的對象, 速度很快
  2. 並發標記(CMS concurrent mark: GC Roots Tracing過程)
  3. 重新標記(CMS remark):修正並發標記期間因用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄
  4. 並發清除(CMS concurrent sweep: 已死對象將會就地釋放)

可以看到,初始標記、重新標記需要STW(stop the world 即:掛起用戶線程)操作。因為最耗時的操作是並發標記和並發清除。所以總體上我們認為CMS的GC與用戶線程是並發運行的。

優點:並發收集、低停頓

缺點:

  1. CMS默認啟動的回收線程數=(CPU數目+3)*4
    當CPU數>4時, GC線程最多占用不超過25%的CPU資源, 但是當CPU數<=4時, GC線程可能就會過多的占用用戶CPU資源, 從而導致應用程序變慢, 總吞吐量降低.
  2. 無法清除浮動垃圾(GC運行到並發清除階段時用戶線程產生的垃圾),因為用戶線程是需要內存的,如果浮動垃圾施放不及時,很可能就造成內存溢出,所以CMS不能像別的垃圾收集器那樣等老年代幾乎滿了才觸發,CMS提供了參數-XX:CMSInitiatingOccupancyFraction來設置GC觸發百分比(1.6后默認92%),當然我們還得設置啟用該策略-XX:+UseCMSInitiatingOccupancyOnly
  3. 因為CMS采用標記-清除算法,所以可能會帶來很多的碎片,如果碎片太多沒有清理,jvm會因為無法分配大對象內存而觸發GC,因此CMS提供了-XX:+UseCMSCompactAtFullCollection參數,它會在GC執行完后接着進行碎片整理,但是又會有個問題,碎片整理不能並發,所以必須單線程去處理,所以如果每次GC完都整理用戶線程stop的時間累積會很長,所以XX:CMSFullGCsBeforeCompaction參數設置隔幾次GC進行一次碎片整理(默認為0)。
G1

同優秀的CMS垃圾回收器一樣,G1也是關注最小時延的垃圾回收器,也同樣適合大尺寸堆內存的垃圾收集,官方也推薦使用G1來代替選擇CMS。G1最大的特點是引入分區的思路,弱化分代的概念,合理利用垃圾收集各個周期的資源,解決了其他收集器甚至CMS的眾多缺陷。

Java崗 面試考點精講(基礎篇02期)

因為每個區都有E、S、O代,所以在G1中,不需要對整個Eden等代進行回收,而是尋找可回收對象比較多的區,然后進行回收(雖然也需要STW操作,但是花費的時間是很少的),保證高效率。

新生代收集

G1的新生代收集跟ParNew類似,如果存活時間超過某個閾值,就會被轉移到S/O區。

年輕代內存由一組不連續的heap區組成, 這種方法使得可以動態調整各代區域的大小

老年代收集

分為以下幾個階段:

  1. 初始標記 (Initial Mark: Stop the World Event)
    在G1中, 該操作附着一次年輕代GC, 以標記Survivor中有可能引用到老年代對象的Regions.
  2. 掃描根區域 (Root Region Scanning: 與應用程序並發執行)
    掃描Survivor中能夠引用到老年代的references. 但必須在Minor GC觸發前執行完
  3. 並發標記 (Concurrent Marking : 與應用程序並發執行)
    在整個堆中查找存活對象, 但該階段可能會被Minor GC中斷
  4. 重新標記 (Remark : Stop the World Event)
    完成堆內存中存活對象的標記. 使用snapshot-at-the-beginning(SATB, 起始快照)算法, 比CMS所用算法要快得多(空Region直接被移除並回收, 並計算所有區域的活躍度).
  5. 清理 (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. 初始化

  1. 分配完內存后要對對象的頭(Object Header)進行初始化,這新信息包括:該對象對應類的元數據、該對象的GC代、對象的哈希碼。
  2. 抽象數據類型默認初始化為null,基本數據類型為0,布爾為false....

5. 調用對象的初始化方法

也就是執行構造方法。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM