開篇介紹
大家好橄仆,我是Java最全面試題庫
的提褲姐妙色,今天這篇是面試系列的第十七篇,主要總結(jié)了JavaSE中JVM
相關(guān)面試題僧著,這篇是JVM系列的第一篇,主要講解JVM的內(nèi)存模型
障簿,第二篇主要講解垃圾回收盹愚。在后續(xù),會沿著第一篇開篇的知識線路一直總結(jié)下去站故,做到日更皆怕!如果我能做到百日百更毅舆,希望你也可以跟著百日百刷,一百天養(yǎng)成一個好習慣愈腾。
說一下JVM內(nèi)存模型及分區(qū)憋活,每個區(qū)放什么?
JVM分為:
- 堆區(qū)
- 棧區(qū)
- 方法區(qū)
①方法區(qū):主要是存儲類信息虱黄,常量池(static量和 static變量)悦即,編譯后的代碼(字節(jié)碼)等數(shù)據(jù)
②堆:初始化的對象,成員變量(那種非 static的變量)橱乱,所有的對象實例和數(shù)組都要在堆上分配
③棧:棧的結(jié)構(gòu)是棧幀組成的辜梳,調(diào)用一個方法就壓入一幀,幀上面存儲局部變量表泳叠,操作數(shù)棧作瞄,方法出口等信息,局部變量表存放的是8大基礎類型加上一個引用類型析二,所以還是一個指向地址的指針
④本地方法棧:主要為 Native方法服務
⑤程序計數(shù)器:記錄當前線程執(zhí)行的行號
總結(jié):初始化的對象放在堆里面粉洼,引用放在棧里面,class類信息常量池(static常量和 static變量)等放在方法區(qū)
JVM內(nèi)存區(qū)域主要分為:
- 線程私有區(qū)(程序計數(shù)器叶摄、虛擬機棧属韧、本地方法區(qū))
- 線程共享區(qū)(Java堆、方法區(qū))
- 直接內(nèi)存
說一下Java內(nèi)存分配
- 寄存器:無法控制蛤吓。
- 靜態(tài)域: static定義的靜態(tài)成員宵喂。
- 常量池:編譯時被確定并保存在
. class文件
中的final常量值
和一些文本修飾的符號引用(類和接口的全限定名,字段的名稱和描述符,方法和名稱和描述符)。 - 非RAM存儲:硬盤等永久存儲空間会傲。
- 堆內(nèi)存:new創(chuàng)建的對象和數(shù)組,由Java虛擬機自動垃圾回收器管理,存取速度慢锅棕。
- 棧內(nèi)存:基本類型的變量和對象的引用變量(堆內(nèi)存空間的訪問地址),速度快,可以共享,但是大小與生存期必須確定,缺乏靈活性。
新生代中為什么要分為Eden和Survivor淌山?
如果沒有Survivor裸燎,Eden區(qū)每進行一次Minor GC
,存活的對象就會被送到老年代泼疑。老年代很快被填滿德绿,觸發(fā)Major GC
.老年代的內(nèi)存空間遠大于新生代,進行一次Full GC
消耗的時間比Minor GC
長得多,所以需要分為Eden和Survivor退渗。
Survivor的存在意義移稳,就是減少被送到老年代的對象,進而減少Full GC的發(fā)生会油,Survivor的預篩選保證个粱,只有經(jīng)歷16
次Minor GC還能在新生代中存活的對象,才會被送到老年代翻翩。
設置兩個Survivor區(qū)最大的好處就是解決了碎片化
都许,剛剛新建的對象在Eden中稻薇,經(jīng)歷一次Minor GC,Eden中的存活對象就會被移動到第一塊survivor space S0梭稚,Eden被清空颖低;等Eden區(qū)再滿了,就再觸發(fā)一次Minor GC弧烤,Eden和S0中的存活對象又會被復制送入第二塊survivor space S1(這個過程非常重要忱屑,因為這種復制算法保證了S1中來自S0和Eden兩部分的存活對象占用連續(xù)的內(nèi)存空間,避免了碎片化的發(fā)生)
對象如何晉升到老年代?
1暇昂、大對象直接進入老年態(tài)
2莺戒、經(jīng)過一次Minor GC,年齡+1急波,若年齡超過一定限制(15)从铲,則被晉升到老年態(tài)。即長期存活的對象進入老年態(tài)
元數(shù)據(jù)區(qū)存放的是什么澄暮?會oom嗎名段?
Perm Space中保存的是加載class文件
。
會引起OutOfMemory泣懊,出現(xiàn)異成毂伲可以設置 -XX:PermSize
的大小。
JDK 1.8后馍刮,字符串常量不存放在永久代信夫,而是在堆內(nèi)存中,JDK1.8以后沒有永久代概念卡啰,而是用元空間替代静稻,元空間不存在虛擬機中,二是使用本地內(nèi)存匈辱。
jvm加載class原理
JVM中類的裝載是由類加載器(ClassLoader)
和它的子類
來實現(xiàn)的振湾,Java中的類加載器是一個重要的Java運行時系統(tǒng)組件,它負責在運行時查找和裝入類文件中的類亡脸。
由于Java的跨平臺性恰梢,經(jīng)過編譯的Java源程序并不是一個可執(zhí)行程序,而是一個或多個類文件梗掰。當Java程序需要使用某個類時,JVM會確保這個類已經(jīng)被加載嗅回、連接(驗證及穗、準備和解析)和初始化。
類的加載是指把類的.class文件中的數(shù)據(jù)讀入到內(nèi)存中绵载,通常是創(chuàng)建一個字節(jié)數(shù)組讀入.class文件埂陆,然后產(chǎn)生與所加載類對應的Class對象苛白。加載完成后,Class對象還不完整焚虱,所以此時的類還不可用购裙。當類被加載后就進入連接階段,這一階段包括驗證
鹃栽、準備
(為靜態(tài)變量分配內(nèi)存并設置默認的初始值)和解析
(將符號引用替換為直接引用)三個步驟躏率。最后JVM對類進行初始化,包括:
①如果類存在直接的父類并且這個類還沒有被初始化民鼓,那么就先初始化父類薇芝;
②如果類中存在初始化語句,就依次執(zhí)行這些初始化語句丰嘉。類的加載是由類加載器完成的夯到,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)饮亏、系統(tǒng)加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)耍贾。從Java 2(JDK 1.2)開始,類加載過程采取了父親委托機制(PDM)路幸。PDM更好的保證了Java平臺的安全性荐开,在該機制中,JVM自帶的Bootstrap是根加載器劝赔,其他的加載器都有且僅有一個父類加載器誓焦。類的加載首先請求父類加載器加載,父類加載器無能為力時才由其子類加載器自行加載着帽。JVM不會向Java程序提供對Bootstrap的引用杂伟。
哪些區(qū)域可能會發(fā)生oom?
內(nèi)存區(qū)域 | 是否線程私有 | 是否會發(fā)生OOM |
---|---|---|
程序計數(shù)器 | 是 | 否 |
虛擬機棧 | 是 | 是 |
本地方法棧 | 是 | 是 |
方法區(qū) | 否 | 是 |
直接內(nèi)存 | 否 | 是 |
堆 | 否 | 是 |
Java堆的結(jié)構(gòu)是什么樣子的?
JVM的堆是運行時數(shù)據(jù)區(qū)仍翰,所有類的實例和數(shù)組都是在堆上分配內(nèi)存赫粥。
它在JVM啟動的時候被創(chuàng)建。
對象所占的堆內(nèi)存是由自動內(nèi)存管理系統(tǒng)也就是垃圾收集器回收予借。
堆內(nèi)存是由存活和死亡的對象組成的越平。
存活的對象是應用可以訪問的,不會被垃圾回收。
死亡的對象是應用不可訪問尚且還沒有被垃圾收集器回收掉的對象灵迫。
一直到垃圾收集器把這些對象回收掉之前,他們會一直占據(jù)堆內(nèi)存空間秦叛。