一.jvm內存佈局
- 程序計數器:當前線程正在執行的字節碼的行號指示器,線程私有,唯一一個沒有規定任何內存溢出錯誤的情況的區域。
- Java虛擬機棧:線程私有,描述Java方法執行的內存模型,每個方法運行時都會創建一個棧幀,存放局部變量表、操作數棧、動態鏈接、方法出口等信息,每個方法的運行到結束對應一個棧幀的入棧和出棧。會有StackOverFlowError異常(申請的棧深度大於虛擬機所允許深度)和OutOfMemoryError異常(線程無法申請到足夠內存)。
- 本地方法棧:功能與Java虛擬機棧相同,不過是爲Native方法服務。
- java堆:線程共享,存放實例對象和數組對象,申請空間不足拋出OutOfMemoryError異常。
- 方法區:線程共享,存儲已被虛擬機加載的類的類信息、常量、靜態變量、編譯後的代碼;運行時常量池存放class文件中描述的符號引用和直接引用,具有動態性。方法空間不足時拋出OutOfMemoryError異常。
- 直接內存:JVM規範之外的,NIO類引入了一種基於通道和緩衝區的I/O方式,可使用Native函數庫直接分配內存,通過一個存儲在Java堆中的DirectByteBuffer對象作爲這塊內存的引用進行操作,避免了在Java堆和Native堆中來回複製數據。
二.垃圾回收算法與垃圾回收器
垃圾收集算法:
- 標記-清除算法:將所有需要回收的對象先進行標記,標記結束後對標記的對象進行回收,效率低,會造成大量碎片。
- 複製算法:將內存分爲兩塊大小相等的空間,每次只用其中一塊,若一塊內存用完了,就將這塊內存中活着的對象複製到另一快內存中,將已使用的進行清除。不會產生碎片,但是會浪費一定的內存空間。堆的年輕代使用此算法,因爲年輕代對象多爲生存週期比較短的對象。年輕代將空間分爲一個Eden和兩個survivor,每次只使用Eden加一個survivor,回收時,將Eden和survivor中存活的對象複製到另一個survivor上,最後清理Eden和survivor。當Eden與survivor存活對象大於另一個survivor空間大小則需要老年代來擔保。
- 標記-整理算法:標記階段與標記-清除算法相同,標記完成後將所有存活對象向一端移動,然後清除掉端邊界外對象。
- 分代收集算法:根據對象存活週期分爲將內存分爲新生代與老年代,新生代採取複製算法,老年代採用標記清除或標記整理算法。
垃圾回收器:
- Serial收集器:單線程,垃圾回收時需要停下所有的線程工作。
- ParNew收集器:Serial的多線程版本。
- Parallel Scavenge收集器:年輕代,多線程並行收集。設計目標是實現一個可控的吞吐量(cpu運行代碼時間/cpu消耗的總時間)。
- Serial Old收集器:Serial老年代版本。
- CMS:目標是獲得最短回收停頓時間,基於標記清除算法,整個過程四個步驟:初始標記(標記GCRoot直接關聯對象,速度很快)、併發標記(從GCRoot向下標記)、重新標記(併發標記過程中發生變化的對象)、併發清除(清除老年代垃圾)。初始標記和重新標記需要停頓所有用戶線程。缺點:無法處理浮動垃圾、有空間碎片的產生、對CPU敏感。
- G1收集器:唯一一個可同時用於老年代與新生代的收集器。採用標記整理算法,將堆分爲不同大小星等的Region,G1追蹤每個region的垃圾堆積的價值大小,然後有一個優先列表,優先回收價值最大的region,避免在整個堆中進行安全區域的垃圾收集,能建立可預測的停頓時間模型。整個過程四個步驟:初始標記、併發標記、最終標記(併發標記階段發生變化的對象的變化記錄寫入線程remembered set log,同時與remembered set合併)、篩選回收(對每個region回收價值和成本拍尋,得到一個最好的回收方案並回收)。
三.垃圾回收對象時程序的邏輯是否可以繼續執行
不同回收器不同:Serial、ParNew會暫停用戶所有線程工作;CMS、G1會在某一階段暫停用戶線程。
內存分配策略
- 對象優先在Eden分配:若Eden無空間,Java虛擬機發起一次Minor GC。
- 大對象直接進入老年代:大對象指需要大量連續內存空間的對象(如長數組、長字符串)
- 長期存活的對象進入老年代:每個對象有一個對象年齡計數器,age=15晉升爲老年代。age+1的兩個情況:對象在Eden出生並經過一次Minor GC存活且被survivor容納;在survivor區經歷過一次minor GC。
四.空間分配擔保
- 在Minor GC之前,先檢查老年代最大可用連續空間是否大於新生代所有空間總和,成立則此次GC安全
- 不成立,查看是否允許擔保失敗設置爲true,不允許則進行Full GC
- 允許,看老年代最大可用連續空間是否大於歷次晉升到老年代對象的平均大小,不成立則Full GC
- 成立,則進行Minor GC
五.Java中的引用
- 強引用:new這類引用,只要強引用在,對象永遠不會被回收。
- 軟引用:描述有用但非必需的對象,在內存溢出之前,會把這些對象列入回收範圍內進行第二次垃圾回收。
- 弱引用:描述非必需對象,只存活到下一次垃圾回收前。
- 虛引用:不會對生存時間造成影響,不能通過虛引用獲得對象實例,只是在被虛引用的對象被回收時受到一個系統通知。
六.簡述minor gc和full gc
- Minor GC:從新生代回收內存,關鍵是Eden區內存不足,造成不足的原因是Java對象大部分是朝生夕死(java局部對象),而死掉的對象就需要在合適的時機被JVM回收
- Major GC:從老年代回收內存,一般比Minor GC慢10倍以上。
- Full GC:對整個堆來說的,出現Full GC通常伴隨至少一次Minor GC,但非絕對。Full GC被觸發的時候:老年代內存不足;持久代內存不足;統計得到的Minor GC晉升到老年代平均大小大於老年代空間。
七.java虛擬機new一個對象的創建過程
- 在常量池中查看是否有new的參數對應的類的符號引用,並檢查這個符號引用對應的類是否被加載、解析、初始化
- 加載後,爲新對象分配內存空間,對象多需要的內存大小在類被加載之後就被確定(堆內分配內存:指針碰撞、空閒列表)。
- 將分配的空間初始化爲零值。
- 對對象頭進行必要設置(實例是哪個類的實例、類的元信息數據、GC分代年齡等)。
- 執行方法,按照程序的值初始化。
八.java中的類加載機制
Java虛擬機中類加載過程:加載、驗證、準備、解析、初始化。
- 加載:通過一個類的全限名來獲取定義此類的二進制字節流;將這個字節流代表的靜態存儲結構轉換爲方法區的的動態存儲結構;在內存中生成一個代表此類的java.lang.Class對象,作爲方法區中這個類的訪問入口。
- 驗證:驗證class文件中的字節流是否符合Java虛擬機規範,包括文件格式、元數據等。
- 準備:爲類變量分配內存並設置類變量初始值,分配內存在方法區。
- 4.解析:將常量池中符號引用替換爲直接引用的過程;符號引用與虛擬機實現的內存佈局無關,是使用一組符號來描述所引用的目標。class文件中不會保存各個方法的最終佈局信息,所以這些符號引用不經過轉化是無法得到真正的內存入口地址;直接引用與虛擬機實現的內存佈局有關,可以是直接指向目標的指針,偏移量或指向目標的句柄。此過程主要是靜態鏈接,方法主要爲靜態方法和私有方法。
- 5.初始化:真正執行類中定義的Java代碼。初始化執行類的方法,該方法由編譯器自動收集類中所有類變量的賦值動作和靜態語句塊的語句合併產生,且保證子類的clinit調用之前會先執行父類的clinit方法,clinit可以不存在(如沒有類變量和靜態語句塊)。
九.雙親委派模型
java中類加載器主要用於實現類的加載,Java中的類和類加載器一起唯一確定類在JVM中的一致性。
系統提供的類加載器:啓動類加載器、擴展類加載器、應用程序類加載器。
- 啓動類加載器:用C++實現,是JVM的一部分,其他加載器使用Java實現,獨立於JVM。主要負責加載<JAVA_HOME>\lib目錄下的類庫或被-Xbootclasspath參數指定的路徑中的類庫,應用程序不能使用該類加載器。
- 擴展類加載器:負責加載<JAVA_HOME>/lib/ext目錄下或者類系統變量java.ext.dirs指定路徑下的類庫,開發者課直接使用。
- 應用程序類加載器:主要負責加載classpath下的類庫,若應用程序沒有自定義類加載器,默認使用此加載器
雙親委派模型要求除了啓動類加載器,其他類加載器都有自己的父類加載器,使用組合關係來實現複用父類加載器。過程:若一個類加載器收到類加載請求,會把此請求委派給父類加載器去完成,每層都是如此,因此所有的加載請求最後都會傳到啓動類加載器;只有當父類加載器反饋不能加載,纔會把此請求交給子類完成。
好處:使得java類伴隨他的類加載器有了優先級;保證Java程序運行的穩定性
十.簡述分派
包括靜態分派與動態分派
- 靜態分派:發生在編譯時期,所有依賴靜態類型來定位方法執行版本的分派稱爲靜態分派,典型應用爲方法重載。
- 動態分派:在運行期根據實際類型確定方法執行版本的分派過程。典型應用爲方法重寫,實現是在方法去中建立方法表,若子類中沒有重寫父類方法,則子類虛方法表中該方法的入口地址與父類指向相同,否則子類方法表中地址會替換爲指向子類重寫的方法的入口地址。
十一.對象的內存佈局
對象內存佈局分爲三部分:對象頭、實例數據、對齊填充。
對象頭包含兩部分:
- 存儲對象自身運行時數據:哈希碼、分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等
- 對象指向它的類元數據指針–類型指針
實例數據:程序代碼中所定義的各種類型的字段內容
對齊填充:不是必然存在,僅起到佔位符作用(對象大小必須是8子節整數倍)
十二.虛擬機棧中的各個部分
- 局部變量表:存放方法參數和方法內部定義的局部變量,以變量槽Slot爲基本單位,一個Slot可以存放32位以內的數據類型,可重用。
- 操作數棧:先入後出,32位數據類型所佔棧容量爲1,64爲數據類型所佔棧容量爲2
- 動態鏈接:常量池中符號引用有一部分在每次運行期間轉換爲直接引用,這部分稱爲動態鏈接。(一部分在類加載階段或第一次使用時轉換爲直接引用—靜態解析)
- 方法返回地址:方法執行後退出的兩種方式:正常完成出口(執行引擎遇到任意一個返回的字節碼指令)和異常完成出口(在方法執行過程中遇到異常且此異常未被處理)。兩種方式都需要返回到方法被調用的位置程序才能繼續執行(正常退出時調用者的PC計數器的值可以作爲返回地址且棧幀中很可能保存這個計數器值;異常退出返回地址要通過異常處理器表來確定,棧幀中一般不會保存)。
十三.Java內存模型的happen before原則
如果兩個操作存在happens-before關係,那麼前一個操作的結果就會對後面一個操作可見,是定義的兩個操作之間的偏序關係,常見的規則:
- 程序順序規則:一個線程中每個操作,happens-before於該線程中的任意後續操作
- 監視器鎖規則:對一個鎖的解鎖,happens-before於隨後這個鎖的加鎖
- volatile變量規則:對一個volatile域的寫,happens-before於任意後續對這個域的讀
- 傳遞性:若A happens-before B,B happens-before C,則A happens-before C
- start()規則:如果線程A執行ThreadB.start(),那麼A線程的ThreadB.start()操作happens-before於線程B中的任意操作。
- join()規則:若線程A 執行ThreadB.join()併成功返回,則線程B的任意操作happens-before於線程A從ThreadB.jion()操作返回成功。
十四.java中方法區存放哪些東西?jvm如何控制方法區的大小以及內存溢出的原因和解決
方法區大小不是固定的,jvm可根據需要動態調整。方法區主要存放類信息、常量、靜態變量、編譯後的代碼。
控制方法區大小:減少程序中class數量、儘量使用較少的靜態變量
修改:-XX:MaxPerSize調大
StackOverflowError異常:線程的方法嵌套調用層次太多,隨着Java棧中楨的增多,最終會由於該線程Java棧中所有棧幀總和大於-Xss設置的值而產生此異常。
十五.jvm OutMemory的種類
- 堆溢出:被緩存的實例對象,大的map,list引用大的對象等
- 棧溢出:棧幀太多
- 方法區溢出:加載很多類會有可能出現,GC不會在主程序運行期對此區域進行清理,可通過設置jvm啓動參數解決:-XX:MaxPermSize=256m
十六.jvm如何判斷對象是否失效?可達性分析是否可以解決循環引用
- 引用計數器算法:給對象添加一個引用計數器,當被引用時給計數器加1,引用失效減1,當爲0時對象失效。實現簡單,判定效率高,無法解決循環引用問題。
- 可達性分析算法:將一系列GC Root作爲起始點,從這些節點開始向下搜索,所走過路徑稱爲引用鏈,若一個對象無引用鏈,則判斷是否執行finalize()方法,若finalize()被覆蓋並且沒被JVM調用過,則執行此方法,執行後若還無引用鏈,則對象失效。
可以作爲GC Root的對象:
- 虛擬機棧中引用的對象
- 方法區中類靜態屬性引用的對象
- 方法區中常量引用的對象
- 本地方法棧中Native方法引用的對象
除了瞭解以上的16到JVM面試題,我們還需要掌握JVM的相關技術點。
現在互聯網公司面試的時候都會問到JVM,但是僅僅掌握JVM是不夠的,我們需要掌握更多的基礎知識,這是我整理的一些需要掌握的知識技術點,分享給大家:
需要思維導圖格式的可以私信我“架構”
架構師築基知識點
1.1. JVM性能調優
- 性能優化如何理解
- JVM內存管理機制
- JVM執行子系統
- 程序編譯與代碼優化
- 實戰調優案例與解決方法
1.2. Java程序性能優化
- 優雅的創建對象
- 注意對象的通用方法
- 類的設計陷阱
- 泛型需要注意的問題
- Java方法的那些坑
- 程序設計的通用規則
1.3. Tomcat
- Tomcat線程模型分析
- Tomcat生產環境配置
- Tomcat運行機制及框架
- Tomcat針對併發優化
- Tomcat針對內存優化
- 手寫Tomcat實戰
1.4. 併發編程進階
- 線程基礎
- 原子操作類和CAS
- Lock、Condition和顯示鎖
- AbstractQueuedSynchronizer分析
- 併發工具類和併發容器
- 線程池和Executor框架
- 實現原理和Java內存模型
- 線程安全
- 併發項目實戰
1.5. Mysql
- 探析BTree機制
- 執行計劃深入分析
- Mysql索引優化詳解
- 慢查詢分析與SQL優化
1.6. 高性能Netty框架
- Netty簡介
- I/O 演進之路及NIO 入門
- Netty 開發環境搭建安裝
- TCP 粘包/拆包問題的解決之道
- 分隔符和定長解碼器的應用
- Netty 多協議開發和應用
- WebSocket 協議開發
- Netty源碼分析
1.7. Linux基礎與進階
- Linux入門安裝
- Linux注意事項
- Linux基礎指令
- Linux Jdk1.8環境安裝及操作指令
- Linux Tomcat安裝與停啓
- Linux下Docker進階講解
- Linux下Docker與Tomcat集成實戰
針對以上的技術點,有十餘年Java經驗的我有自己的一些心得,也錄製了一些視頻,解析這些技術。一.jvm內存佈局
- 程序計數器:當前線程正在執行的字節碼的行號指示器,線程私有,唯一一個沒有規定任何內存溢出錯誤的情況的區域。
- Java虛擬機棧:線程私有,描述Java方法執行的內存模型,每個方法運行時都會創建一個棧幀,存放局部變量表、操作數棧、動態鏈接、方法出口等信息,每個方法的運行到結束對應一個棧幀的入棧和出棧。會有StackOverFlowError異常(申請的棧深度大於虛擬機所允許深度)和OutOfMemoryError異常(線程無法申請到足夠內存)。
- 本地方法棧:功能與Java虛擬機棧相同,不過是爲Native方法服務。
- java堆:線程共享,存放實例對象和數組對象,申請空間不足拋出OutOfMemoryError異常。
- 方法區:線程共享,存儲已被虛擬機加載的類的類信息、常量、靜態變量、編譯後的代碼;運行時常量池存放class文件中描述的符號引用和直接引用,具有動態性。方法空間不足時拋出OutOfMemoryError異常。
- 直接內存:JVM規範之外的,NIO類引入了一種基於通道和緩衝區的I/O方式,可使用Native函數庫直接分配內存,通過一個存儲在Java堆中的DirectByteBuffer對象作爲這塊內存的引用進行操作,避免了在Java堆和Native堆中來回複製數據。
二.垃圾回收算法與垃圾回收器
垃圾收集算法:
- 標記-清除算法:將所有需要回收的對象先進行標記,標記結束後對標記的對象進行回收,效率低,會造成大量碎片。
- 複製算法:將內存分爲兩塊大小相等的空間,每次只用其中一塊,若一塊內存用完了,就將這塊內存中活着的對象複製到另一快內存中,將已使用的進行清除。不會產生碎片,但是會浪費一定的內存空間。堆的年輕代使用此算法,因爲年輕代對象多爲生存週期比較短的對象。年輕代將空間分爲一個Eden和兩個survivor,每次只使用Eden加一個survivor,回收時,將Eden和survivor中存活的對象複製到另一個survivor上,最後清理Eden和survivor。當Eden與survivor存活對象大於另一個survivor空間大小則需要老年代來擔保。
- 標記-整理算法:標記階段與標記-清除算法相同,標記完成後將所有存活對象向一端移動,然後清除掉端邊界外對象。
- 分代收集算法:根據對象存活週期分爲將內存分爲新生代與老年代,新生代採取複製算法,老年代採用標記清除或標記整理算法。
垃圾回收器:
- Serial收集器:單線程,垃圾回收時需要停下所有的線程工作。
- ParNew收集器:Serial的多線程版本。
- Parallel Scavenge收集器:年輕代,多線程並行收集。設計目標是實現一個可控的吞吐量(cpu運行代碼時間/cpu消耗的總時間)。
- Serial Old收集器:Serial老年代版本。
- CMS:目標是獲得最短回收停頓時間,基於標記清除算法,整個過程四個步驟:初始標記(標記GCRoot直接關聯對象,速度很快)、併發標記(從GCRoot向下標記)、重新標記(併發標記過程中發生變化的對象)、併發清除(清除老年代垃圾)。初始標記和重新標記需要停頓所有用戶線程。缺點:無法處理浮動垃圾、有空間碎片的產生、對CPU敏感。
- G1收集器:唯一一個可同時用於老年代與新生代的收集器。採用標記整理算法,將堆分爲不同大小星等的Region,G1追蹤每個region的垃圾堆積的價值大小,然後有一個優先列表,優先回收價值最大的region,避免在整個堆中進行安全區域的垃圾收集,能建立可預測的停頓時間模型。整個過程四個步驟:初始標記、併發標記、最終標記(併發標記階段發生變化的對象的變化記錄寫入線程remembered set log,同時與remembered set合併)、篩選回收(對每個region回收價值和成本拍尋,得到一個最好的回收方案並回收)。
三.垃圾回收對象時程序的邏輯是否可以繼續執行
不同回收器不同:Serial、ParNew會暫停用戶所有線程工作;CMS、G1會在某一階段暫停用戶線程。
內存分配策略
- 對象優先在Eden分配:若Eden無空間,Java虛擬機發起一次Minor GC。
- 大對象直接進入老年代:大對象指需要大量連續內存空間的對象(如長數組、長字符串)
- 長期存活的對象進入老年代:每個對象有一個對象年齡計數器,age=15晉升爲老年代。age+1的兩個情況:對象在Eden出生並經過一次Minor GC存活且被survivor容納;在survivor區經歷過一次minor GC。
四.空間分配擔保
- 在Minor GC之前,先檢查老年代最大可用連續空間是否大於新生代所有空間總和,成立則此次GC安全
- 不成立,查看是否允許擔保失敗設置爲true,不允許則進行Full GC
- 允許,看老年代最大可用連續空間是否大於歷次晉升到老年代對象的平均大小,不成立則Full GC
- 成立,則進行Minor GC
五.Java中的引用
- 強引用:new這類引用,只要強引用在,對象永遠不會被回收。
- 軟引用:描述有用但非必需的對象,在內存溢出之前,會把這些對象列入回收範圍內進行第二次垃圾回收。
- 弱引用:描述非必需對象,只存活到下一次垃圾回收前。
- 虛引用:不會對生存時間造成影響,不能通過虛引用獲得對象實例,只是在被虛引用的對象被回收時受到一個系統通知。
六.簡述minor gc和full gc
- Minor GC:從新生代回收內存,關鍵是Eden區內存不足,造成不足的原因是Java對象大部分是朝生夕死(java局部對象),而死掉的對象就需要在合適的時機被JVM回收
- Major GC:從老年代回收內存,一般比Minor GC慢10倍以上。
- Full GC:對整個堆來說的,出現Full GC通常伴隨至少一次Minor GC,但非絕對。Full GC被觸發的時候:老年代內存不足;持久代內存不足;統計得到的Minor GC晉升到老年代平均大小大於老年代空間。
七.java虛擬機new一個對象的創建過程
- 在常量池中查看是否有new的參數對應的類的符號引用,並檢查這個符號引用對應的類是否被加載、解析、初始化
- 加載後,爲新對象分配內存空間,對象多需要的內存大小在類被加載之後就被確定(堆內分配內存:指針碰撞、空閒列表)。
- 將分配的空間初始化爲零值。
- 對對象頭進行必要設置(實例是哪個類的實例、類的元信息數據、GC分代年齡等)。
- 執行方法,按照程序的值初始化。
八.java中的類加載機制
Java虛擬機中類加載過程:加載、驗證、準備、解析、初始化。
- 加載:通過一個類的全限名來獲取定義此類的二進制字節流;將這個字節流代表的靜態存儲結構轉換爲方法區的的動態存儲結構;在內存中生成一個代表此類的java.lang.Class對象,作爲方法區中這個類的訪問入口。
- 驗證:驗證class文件中的字節流是否符合Java虛擬機規範,包括文件格式、元數據等。
- 準備:爲類變量分配內存並設置類變量初始值,分配內存在方法區。
- 4.解析:將常量池中符號引用替換爲直接引用的過程;符號引用與虛擬機實現的內存佈局無關,是使用一組符號來描述所引用的目標。class文件中不會保存各個方法的最終佈局信息,所以這些符號引用不經過轉化是無法得到真正的內存入口地址;直接引用與虛擬機實現的內存佈局有關,可以是直接指向目標的指針,偏移量或指向目標的句柄。此過程主要是靜態鏈接,方法主要爲靜態方法和私有方法。
- 5.初始化:真正執行類中定義的Java代碼。初始化執行類的方法,該方法由編譯器自動收集類中所有類變量的賦值動作和靜態語句塊的語句合併產生,且保證子類的clinit調用之前會先執行父類的clinit方法,clinit可以不存在(如沒有類變量和靜態語句塊)。
九.雙親委派模型
java中類加載器主要用於實現類的加載,Java中的類和類加載器一起唯一確定類在JVM中的一致性。
系統提供的類加載器:啓動類加載器、擴展類加載器、應用程序類加載器。
- 啓動類加載器:用C++實現,是JVM的一部分,其他加載器使用Java實現,獨立於JVM。主要負責加載<JAVA_HOME>\lib目錄下的類庫或被-Xbootclasspath參數指定的路徑中的類庫,應用程序不能使用該類加載器。
- 擴展類加載器:負責加載<JAVA_HOME>/lib/ext目錄下或者類系統變量java.ext.dirs指定路徑下的類庫,開發者課直接使用。
- 應用程序類加載器:主要負責加載classpath下的類庫,若應用程序沒有自定義類加載器,默認使用此加載器
雙親委派模型要求除了啓動類加載器,其他類加載器都有自己的父類加載器,使用組合關係來實現複用父類加載器。過程:若一個類加載器收到類加載請求,會把此請求委派給父類加載器去完成,每層都是如此,因此所有的加載請求最後都會傳到啓動類加載器;只有當父類加載器反饋不能加載,纔會把此請求交給子類完成。
好處:使得java類伴隨他的類加載器有了優先級;保證Java程序運行的穩定性
十.簡述分派
包括靜態分派與動態分派
- 靜態分派:發生在編譯時期,所有依賴靜態類型來定位方法執行版本的分派稱爲靜態分派,典型應用爲方法重載。
- 動態分派:在運行期根據實際類型確定方法執行版本的分派過程。典型應用爲方法重寫,實現是在方法去中建立方法表,若子類中沒有重寫父類方法,則子類虛方法表中該方法的入口地址與父類指向相同,否則子類方法表中地址會替換爲指向子類重寫的方法的入口地址。
十一.對象的內存佈局
對象內存佈局分爲三部分:對象頭、實例數據、對齊填充。
對象頭包含兩部分:
- 存儲對象自身運行時數據:哈希碼、分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等
- 對象指向它的類元數據指針–類型指針
實例數據:程序代碼中所定義的各種類型的字段內容
對齊填充:不是必然存在,僅起到佔位符作用(對象大小必須是8子節整數倍)
十二.虛擬機棧中的各個部分
- 局部變量表:存放方法參數和方法內部定義的局部變量,以變量槽Slot爲基本單位,一個Slot可以存放32位以內的數據類型,可重用。
- 操作數棧:先入後出,32位數據類型所佔棧容量爲1,64爲數據類型所佔棧容量爲2
- 動態鏈接:常量池中符號引用有一部分在每次運行期間轉換爲直接引用,這部分稱爲動態鏈接。(一部分在類加載階段或第一次使用時轉換爲直接引用—靜態解析)
- 方法返回地址:方法執行後退出的兩種方式:正常完成出口(執行引擎遇到任意一個返回的字節碼指令)和異常完成出口(在方法執行過程中遇到異常且此異常未被處理)。兩種方式都需要返回到方法被調用的位置程序才能繼續執行(正常退出時調用者的PC計數器的值可以作爲返回地址且棧幀中很可能保存這個計數器值;異常退出返回地址要通過異常處理器表來確定,棧幀中一般不會保存)。
十三.Java內存模型的happen before原則
如果兩個操作存在happens-before關係,那麼前一個操作的結果就會對後面一個操作可見,是定義的兩個操作之間的偏序關係,常見的規則:
- 程序順序規則:一個線程中每個操作,happens-before於該線程中的任意後續操作
- 監視器鎖規則:對一個鎖的解鎖,happens-before於隨後這個鎖的加鎖
- volatile變量規則:對一個volatile域的寫,happens-before於任意後續對這個域的讀
- 傳遞性:若A happens-before B,B happens-before C,則A happens-before C
- start()規則:如果線程A執行ThreadB.start(),那麼A線程的ThreadB.start()操作happens-before於線程B中的任意操作。
- join()規則:若線程A 執行ThreadB.join()併成功返回,則線程B的任意操作happens-before於線程A從ThreadB.jion()操作返回成功。
十四.java中方法區存放哪些東西?jvm如何控制方法區的大小以及內存溢出的原因和解決
方法區大小不是固定的,jvm可根據需要動態調整。方法區主要存放類信息、常量、靜態變量、編譯後的代碼。
控制方法區大小:減少程序中class數量、儘量使用較少的靜態變量
修改:-XX:MaxPerSize調大
StackOverflowError異常:線程的方法嵌套調用層次太多,隨着Java棧中楨的增多,最終會由於該線程Java棧中所有棧幀總和大於-Xss設置的值而產生此異常。
十五.jvm OutMemory的種類
- 堆溢出:被緩存的實例對象,大的map,list引用大的對象等
- 棧溢出:棧幀太多
- 方法區溢出:加載很多類會有可能出現,GC不會在主程序運行期對此區域進行清理,可通過設置jvm啓動參數解決:-XX:MaxPermSize=256m
十六.jvm如何判斷對象是否失效?可達性分析是否可以解決循環引用
- 引用計數器算法:給對象添加一個引用計數器,當被引用時給計數器加1,引用失效減1,當爲0時對象失效。實現簡單,判定效率高,無法解決循環引用問題。
- 可達性分析算法:將一系列GC Root作爲起始點,從這些節點開始向下搜索,所走過路徑稱爲引用鏈,若一個對象無引用鏈,則判斷是否執行finalize()方法,若finalize()被覆蓋並且沒被JVM調用過,則執行此方法,執行後若還無引用鏈,則對象失效。
可以作爲GC Root的對象:
- 虛擬機棧中引用的對象
- 方法區中類靜態屬性引用的對象
- 方法區中常量引用的對象
- 本地方法棧中Native方法引用的對象
除了瞭解以上的16到JVM面試題,我們還需要掌握JVM的相關技術點。
現在互聯網公司面試的時候都會問到JVM,但是僅僅掌握JVM是不夠的,我們需要掌握更多的基礎知識,這是我整理的一些需要掌握的知識技術點,分享給大家:
需要思維導圖格式的可以私信我“架構”
架構師築基知識點
1.1. JVM性能調優
- 性能優化如何理解
- JVM內存管理機制
- JVM執行子系統
- 程序編譯與代碼優化
- 實戰調優案例與解決方法
1.2. Java程序性能優化
- 優雅的創建對象
- 注意對象的通用方法
- 類的設計陷阱
- 泛型需要注意的問題
- Java方法的那些坑
- 程序設計的通用規則
1.3. Tomcat
- Tomcat線程模型分析
- Tomcat生產環境配置
- Tomcat運行機制及框架
- Tomcat針對併發優化
- Tomcat針對內存優化
- 手寫Tomcat實戰
1.4. 併發編程進階
- 線程基礎
- 原子操作類和CAS
- Lock、Condition和顯示鎖
- AbstractQueuedSynchronizer分析
- 併發工具類和併發容器
- 線程池和Executor框架
- 實現原理和Java內存模型
- 線程安全
- 併發項目實戰
1.5. Mysql
- 探析BTree機制
- 執行計劃深入分析
- Mysql索引優化詳解
- 慢查詢分析與SQL優化
1.6. 高性能Netty框架
- Netty簡介
- I/O 演進之路及NIO 入門
- Netty 開發環境搭建安裝
- TCP 粘包/拆包問題的解決之道
- 分隔符和定長解碼器的應用
- Netty 多協議開發和應用
- WebSocket 協議開發
- Netty源碼分析
1.7. Linux基礎與進階
- Linux入門安裝
- Linux注意事項
- Linux基礎指令
- Linux Jdk1.8環境安裝及操作指令
- Linux Tomcat安裝與停啓
- Linux下Docker進階講解
- Linux下Docker與Tomcat集成實戰
針對以上的技術點,有十餘年Java經驗的我有自己的一些心得,也錄製了一些視頻,解析這些技術。
分享給喜歡Java,喜歡編程,有夢想成爲架構師的程序員們,希望能夠幫助到你們。