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