一文掌握 JVM 面試要點(diǎn)
之前發(fā)表的「吃透MySQL系列」專欄與「吃透Redis系列」專欄收到很多小伙伴的來(lái)信,回饋效果都很好。但是反應(yīng)關(guān)于JVM的文章很少。
因此,我打算開一個(gè)「吃透JVM系列」的專欄。
之前發(fā)過(guò)一篇關(guān)于JVM面試知識(shí)點(diǎn)總結(jié)的文章。但是缺乏系統(tǒng)每個(gè)知識(shí)點(diǎn)的講解,于是我打算以那篇文章為目錄根據(jù)每個(gè)知識(shí)點(diǎn)后面為大家詳細(xì)講解,不過(guò)需要等吃透MySQL系列講解完。
今天,先公布之前JVM面試總結(jié)的修訂版,并給大家預(yù)先宣傳一波,「關(guān)注公眾號(hào),持續(xù)閱讀后續(xù)精彩好文」。

本文將作為本專欄「吃透Redis系列」目錄,也是大廠面試標(biāo)準(zhǔn)回答,具體每個(gè)點(diǎn)的詳細(xì)解析會(huì)收錄于本專欄,關(guān)注【小龍coding】,持續(xù)閱
讀后續(xù)精品文章!!
?本文收錄于【面試筆記】,更多付費(fèi)文章,可以后臺(tái)回復(fù)【面試筆記】獲取,【點(diǎn)擊此處試讀】。
?
1、運(yùn)行時(shí)數(shù)據(jù)區(qū)域
「堆」
對(duì)象實(shí)例、數(shù)組
-Xms表示堆初始大小
-Xmx表示堆最大大小
邏輯上連續(xù),線程共享,虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,最大
沒(méi)有內(nèi)存完成實(shí)例分配,且無(wú)法擴(kuò)展,OOM
「方法區(qū)」
存儲(chǔ)已被虛擬機(jī)加載的類型信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼緩存
被線程共享,不會(huì)頻繁GC
「實(shí)現(xiàn)」:jdk7把靜態(tài)變量和字符串常量池移到堆中,jdk8移除永久代,把方法區(qū)移致元空間,它位于本地內(nèi)存。
?注意:JDk6、JDk7方法區(qū)即PermGen(永久代),JDK8方法區(qū)就是MetaSpace(元空間)
?
運(yùn)行時(shí)常量池:
「Class文件存放什么:」
類的版本、字段、方法、接口
常量池表(Constant pool)(存放編譯期生成的各種「字面量」與「符號(hào)引用」)
「字面量」:字面量就是指由字母、數(shù)字等構(gòu)成的字符串或者數(shù)值常量 「符號(hào)引用」:類和接口的全限定類名+字段的名稱與描述符+方法的名稱與描述符
int?a?=?1;//1、2、“abcdefg”就是字面量
int b = 2;//a b c d字段名就是符號(hào)引用,還有方法名、全限定類名等都屬于=符號(hào)引用。
String?c?=?"abcdefg";
String?d?=?"abcdefg";
Class文件存放的常量池表的內(nèi)容將在類加載后存放到方法區(qū)的運(yùn)行時(shí)常量池(「符號(hào)引用轉(zhuǎn)為直接引用」)
「與Class文件常量池區(qū)別」:動(dòng)態(tài)性(可以在運(yùn)行期間將常量放入池(String:intern()))
「OOM」:常量池?zé)o法申請(qǐng)到內(nèi)存JVM常量池解析見下文
「Java虛擬機(jī)棧(棧幀組成)」
局部變量表(存放基本數(shù)據(jù)類型+對(duì)象引用+返回地址)
操作數(shù)棧
動(dòng)態(tài)鏈接
方法出口
異常:
StackOverFlowError:線程請(qǐng)求的棧深度大于虛擬機(jī)允許的
OOM:棧容量動(dòng)態(tài)擴(kuò)展無(wú)足夠內(nèi)存
命令參數(shù):java -Xss2M stackjava
「本地方法棧」:
為虛擬機(jī)使用到的Native方法服務(wù)
「程序計(jì)數(shù)器」:
當(dāng)前執(zhí)行的字節(jié)碼指令,「唯一沒(méi)有OOM的地方」?| 執(zhí)行本地方法時(shí)本地方法計(jì)數(shù)器為NULL |?「線程私有」
「直接內(nèi)存」
2、對(duì)象的創(chuàng)建五種方式
2.1、new-構(gòu)造函數(shù)
2.2、Class類的newInstance方法-構(gòu)造函數(shù)?「(相當(dāng)于用無(wú)參構(gòu)造」)
2.3、Constructor類的newInstance方法-構(gòu)造函數(shù)
(「bClass.getConstructors() 可以按順序獲取所有構(gòu)造函數(shù)」)
2.4、反序列化
2.5、clone
「代碼:」
Constructor相關(guān)
?Class?bClass?=?B.class;
?B?b?=?bClass.newInstance();
?System.out.println(b.getName());
?Constructor?constructor[]=?(Constructor[])?bClass.getConstructors();
?B?b1?=?constructor[0].newInstance("11",22);
?System.out.println(b1.getName()+b1.getAge());
反序列化
//序列化過(guò)程?B需要實(shí)現(xiàn)序列化接口
?ObjectOutputStream?objectOutputStream=new?ObjectOutputStream(new?FileOutputStream("b.txt"));
?objectOutputStream.writeObject(new?B("11",2));
?objectOutputStream.close();
?//反序列化
?ObjectInputStream?objectInputStream=new?ObjectInputStream(new?FileInputStream("b.txt"));
?B?b?=?(B)objectInputStream.readObject();
?System.out.println(b.getName()+b.getAge());
3、對(duì)象創(chuàng)建過(guò)程
1、當(dāng)虛擬機(jī)遇到一條字節(jié)碼new指令時(shí),首先去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號(hào)引用,檢查這個(gè)符號(hào)引用代表的類是否被加載過(guò),若沒(méi)有執(zhí)行相關(guān)類加載過(guò)程。「//類加載檢查」
2、類加載通過(guò)后分配內(nèi)存,若堆內(nèi)存規(guī)整,執(zhí)行指針碰撞分配內(nèi)存,否則使用空閑列表分配;「//分配內(nèi)存」
3、劃分內(nèi)存還需要考慮并發(fā)問(wèn)題,可以CAS同步處理,或則本地線程分配緩沖。「//并發(fā)問(wèn)題處理」
4、然后內(nèi)存空間初始化操作(默認(rèn)值,一)些必要的對(duì)象設(shè)置(元信息、哈希碼),再行init()方法(按照程序員意愿初始化)。
4、對(duì)象的訪問(wèn)定位
「句柄:「指針的指針。堆劃分一塊內(nèi)存作為句柄池(句柄池+實(shí)例池),引用存儲(chǔ)句柄的地址,句柄中包含了」對(duì)象實(shí)例數(shù)據(jù)指針」(指向堆對(duì)象實(shí)例數(shù)據(jù))+「對(duì)象實(shí)例類型指針」(指向方法區(qū)對(duì)象類型數(shù)據(jù))
優(yōu)點(diǎn):穩(wěn)定,對(duì)象移動(dòng)時(shí)只改變句柄中對(duì)象實(shí)例數(shù)據(jù)指針,而引用本身不變指向句柄
「直接指針」:直接指向?qū)ο?,保存?duì)象內(nèi)存起始地址的指針
優(yōu)點(diǎn):訪問(wèn)速度快,一次定位
5、對(duì)象內(nèi)存分配
「指針碰撞」
堆內(nèi)存規(guī)整,將堆分為空閑和使用過(guò)兩部分,空閑的放一邊,用過(guò)的放一邊,中間放一個(gè)指針指向分界處,分配對(duì)象內(nèi)存時(shí)就將指針向空閑部分移動(dòng)相應(yīng)大小
「空閑列表」
堆內(nèi)存不規(guī)整,需借助列表存放可用空間,分配對(duì)象內(nèi)存時(shí)查看列表找到足夠的空間分配給對(duì)象,并更新列表
6、對(duì)象并發(fā)安全問(wèn)題
「分配內(nèi)存需考慮并發(fā)問(wèn)題」:可能正在給對(duì)象A分配內(nèi)存,指針還沒(méi)來(lái)得及修改,對(duì)象B又同時(shí)使用了原來(lái)的指針來(lái)分配內(nèi)存的情況
「CAS+失敗重試保證更新原子性」:對(duì)分配內(nèi)存空間動(dòng)作進(jìn)行同步處理
「本地線程分配緩沖」:每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存(本地線程分配緩沖)哪個(gè)線程要分配內(nèi)存,就在哪個(gè)線程的本地緩沖區(qū)中分配,本地緩沖區(qū)用完了分配新的緩沖區(qū)才需要同步鎖定
7、對(duì)象內(nèi)存布局
java對(duì)象=對(duì)象頭+實(shí)例數(shù)據(jù)+對(duì)齊填充
對(duì)象頭=Mark Word+ 對(duì)象所屬類 的指針組成(如果是數(shù)組對(duì)象,還會(huì)包含長(zhǎng)度)
Mark Word=存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),例如hashCode,GC分代年齡,鎖狀態(tài)標(biāo)志,線程持有的鎖等等
8、OOM異常
JVM堆,無(wú)法給實(shí)例分配內(nèi)存,且無(wú)法擴(kuò)展時(shí),OOM。
方法區(qū)(以及運(yùn)行時(shí)常量池)無(wú)法滿足內(nèi)存分配需求時(shí),OOM.
Java虛擬機(jī)棧+本地方法棧,擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠內(nèi)存,OOM(線程請(qǐng)求深度超過(guò)JVM允許棧深度,StackOverflowException).
9、內(nèi)存泄漏與內(nèi)存溢出
無(wú)用內(nèi)存得不到釋放。程序申請(qǐng)了內(nèi)存,使用完后又不能歸還JVM,造成內(nèi)存泄漏,內(nèi)存泄漏多了就造成內(nèi)存溢出。內(nèi)存溢出——》
OOM(內(nèi)存滿了,沒(méi)有內(nèi)存給實(shí)例分配空間,且無(wú)法擴(kuò)展)
Student?stu=new?Student();
List?stus=new?ArrayList<>();
stus.add(stu);
stu=null;
stu占用的內(nèi)存得不到釋放,stus占用著student.?發(fā)生內(nèi)存泄漏
10、判斷對(duì)象是否是垃圾
「引用計(jì)數(shù)器法:」
對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有個(gè)引用就加一,引用失效減一,當(dāng)為減為0對(duì)象不可用
缺點(diǎn):相互引用,A<-->B,然后A、B已經(jīng)沒(méi)有被其他有用對(duì)象引用,本視為垃圾,但是由于互相引用不能被檢查回收。
「可達(dá)性分析法:」
從GC Roots到該對(duì)象可達(dá)
11、GC Roots
Java虛擬機(jī)棧(棧幀中的本變量表)中引用的對(duì)象 方法區(qū)「常量、靜態(tài)變量」引用的對(duì)象 本地方法棧JNI引用對(duì)象 所有被同步鎖持有的對(duì)象
總結(jié)記憶口訣:兩棧一方法
局部變量表:存放方法參數(shù)和方法內(nèi)部定義的局部變量
12、四種引用狀態(tài)(強(qiáng)軟弱虛)
「強(qiáng)引用」
Object obj=new Object(); StronglyReference ——不會(huì)被回收
「軟引用」
new SoftReference(obj); 描述有用但非必須的對(duì)象——內(nèi)存不夠回收
「弱引用」
WeakReference ?弱引用一定會(huì)被回收,下一次GC回收
「虛引用」
PhantomReference 為了能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知
13、方法區(qū)的回收
「廢棄的常量」
沒(méi)有被引用
如:字面量回收
?一個(gè)字符串“abc”放入常量池,現(xiàn)在沒(méi)有一個(gè)值為"abc"的字符串對(duì)象,也就是 沒(méi)有任何字符串對(duì)象引用常量池中“abc”的常量,且虛擬機(jī)其他地方?jīng)]有引用這個(gè)字面量,如果發(fā)生垃圾回收且有必要時(shí),常量會(huì)被系統(tǒng)清理出常量池 String s1="abc"; Strig s2=new String("abc"); 其他接口,方法,字段,符號(hào)引用類似
?
「無(wú)用類的卸載」
該類的所有實(shí)例被回收,堆中不存在該類及其派生子類的實(shí)例 加載該類的CassLoder被回收 該類對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用
14、垃圾收集算法
「標(biāo)記-清除」
先標(biāo)記后清除,先標(biāo)記垃圾對(duì)象,然后統(tǒng)一回收垃圾對(duì)象 缺點(diǎn):要標(biāo)記和清除,效率不高,還容易出現(xiàn)內(nèi)存碎片化
「復(fù)制算法」(適用新生代—存活率低)
「對(duì)象存活率高時(shí)會(huì)有大量復(fù)制,效率低」?,老年代存活率高,不適用 |(分配擔(dān)保) 將內(nèi)存容量分為大小相等兩部分,先使用一塊內(nèi)存,用完了將還存活的對(duì)象復(fù)制到另一塊內(nèi)存上。由于內(nèi)存被分為兩部分,使得「只能用一半內(nèi)存」
「標(biāo)記-整理」
前面和標(biāo)記清除一樣,先標(biāo)記,但是不會(huì)立刻清除,先把「存活的對(duì)象都移到一端」,然后直接清除掉邊界以外的對(duì)象。不會(huì)出現(xiàn)碎片化。
「分代收集」
堆分為年輕代老年代,新生代存活率低使用復(fù)制算法,老年代存活率高使用標(biāo)記清除/標(biāo)記整理。
15、垃圾收集器
「注重低延遲」
「CMS」:基于標(biāo)記清除的并發(fā)垃圾收集器
初始標(biāo)記:標(biāo)記GC Roots直達(dá)的對(duì)象(「stw」) 并發(fā)標(biāo)記:跟蹤標(biāo)記GC Roots所有可達(dá)對(duì)象 重新標(biāo)記:重新標(biāo)記那些由于并發(fā)標(biāo)記中用戶程序跟到執(zhí)行導(dǎo)致標(biāo)記發(fā)生變化的對(duì)象(「stw」) 并發(fā)清除:清除標(biāo)記垃圾
「優(yōu)點(diǎn)」:支持并發(fā),停頓時(shí)間短
「缺點(diǎn)」:使用標(biāo)記清除算法,空間碎片。并發(fā)標(biāo)記產(chǎn)生浮動(dòng)垃圾。
「G1」:并發(fā)+并行(重新標(biāo)記+篩選回收)
「弱化分代(老年代與年輕代一起回收),引入分區(qū)。將堆分為多個(gè)大小相等區(qū)域分而治之?!?/strong>
初始標(biāo)記:標(biāo)記GC Roots直達(dá)的對(duì)象,并且修改TAMS(Next Top Mark Start)的值,讓下一階段用戶程序并發(fā)運(yùn)行時(shí),能在正確可以用的Region中創(chuàng)建新對(duì)象,「需要停頓線程,但耗時(shí)很短」 并發(fā)標(biāo)記:跟蹤標(biāo)記GC Roots所有可達(dá)對(duì)象 最終標(biāo)記:修正在并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分標(biāo)記記錄,對(duì)象變化記錄在線程Remenbered Set Logs里面,最終標(biāo)記階段需要把Remembered Set Logs的數(shù)據(jù)合并到Remembered Set中,這階段「需要停頓線程」,但可并行執(zhí)行 篩選回收:對(duì)各個(gè)region區(qū)域進(jìn)行回收價(jià)值與成本的排序,根據(jù)用戶期望的GC停頓時(shí)間來(lái)執(zhí)行計(jì)劃(最少時(shí)間回收最多垃圾區(qū)域,停頓用戶線程)
「特點(diǎn):」
「空間整合:「整體來(lái)看是基于“標(biāo)記 - 整理”算法實(shí)現(xiàn)的收集器,從局部(兩個(gè) Region 之間)上來(lái)看是基于“復(fù)制”算法實(shí)現(xiàn)的,這意味著運(yùn)行期間不」會(huì)產(chǎn)生內(nèi)存空間碎片」。
「可預(yù)測(cè)的停頓」:能讓使用者明確指定在一個(gè)長(zhǎng)度為 M 毫秒的時(shí)間片段內(nèi),消耗在 GC 上的時(shí)間不得超過(guò) N 毫秒。
16、G1與CMS區(qū)別
使用范圍:CMS使用在老年代,G1收集范圍新生代與老年代 STW的時(shí)間:CMS注重低延遲,G1可預(yù)測(cè)的停頓 垃圾碎片:CMS使用標(biāo)記清除算法,造成內(nèi)存空間碎片;G1進(jìn)行空間整合使用標(biāo)記-整理,不會(huì)有內(nèi)存空間碎片 垃圾回收過(guò)程 使用場(chǎng)景
「stw」:垃圾回收,暫停所用用戶線程執(zhí)行,避免垃圾回收時(shí)產(chǎn)生新垃圾。
17、類加載過(guò)程
「加載」:根據(jù)類的全限定類名獲取二進(jìn)制字節(jié)流,將字節(jié)流代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)->運(yùn)行時(shí)存儲(chǔ)結(jié)構(gòu),在內(nèi)存生成「Class對(duì)象」,作為方法區(qū)這個(gè)類數(shù)據(jù)訪問(wèn)入口 「驗(yàn)證」:檢驗(yàn)加載的class文件正確性(修飾符、權(quán)限、) 「準(zhǔn)備」:為類的「靜態(tài)變量」分配內(nèi)存,并賦默認(rèn)值 「解析」:將常量池中符號(hào)引用轉(zhuǎn)為直接引用(class文件常量池轉(zhuǎn)至方法區(qū)運(yùn)行時(shí)常量池) 「初始化」:(針對(duì)類變量初始化)對(duì)靜態(tài)變量和靜態(tài)代碼塊執(zhí)行初始化工作
18、類加載器
啟動(dòng)類加載器:加載核心類庫(kù)(JAVA_HOME/lib 如rt.jar) 擴(kuò)展類加載器:加載擴(kuò)展類(JAVA_HOME/jre/lib/ext) 系統(tǒng)類加載器:加載用戶類路徑ClassPath下的類 用戶自定義加載器:繼承java.lang.ClassLoader
19、類加載方式
隱式加載:當(dāng)碰到通過(guò)new 等方式生成對(duì)象時(shí),隱式調(diào)用類裝載器加載對(duì)應(yīng)的類到j(luò)vm中 顯示加載:通過(guò)class.forname()等方法,顯式加載需要的類
20、雙親委派模型
含義:類加載請(qǐng)求來(lái)了,類加載器自己先不加載,先讓父類加載器加載,父類不行自己再來(lái)
「怎樣打破雙親委派機(jī)制:」
自定義類加載器,重寫loadClass方法 線程上下文類加載器
Java涉及SPI機(jī)制的都用線程上下文類加載器。父類加載器請(qǐng)求子類加載器完成加載動(dòng)作
SPI機(jī)制:為接口找尋服務(wù)(jdbc)
?SPI約定:服務(wù)提供者為接口提供接口實(shí)現(xiàn)后,會(huì)在jar包的META-INF/service/目錄下創(chuàng)建一個(gè)以服務(wù)接口命名的文件。
?
JDBC4.0使用SPI機(jī)制,DriverManager需要去jar包下的META-INF/services/java.sql.Driver目錄下去尋找對(duì)應(yīng)的Driver加載,但是
DriverManager在rt.jar中,使用啟動(dòng)類加載器(BootStrapClassLoader),它需要調(diào)用服務(wù)提供者放在classpath下的類,啟動(dòng)類加載器無(wú)
法加載,就只得使用線程上下文加載器,讓父類加載器調(diào)用子類加載器完成,打破了雙親委派機(jī)制。
「好處」:避免類重復(fù)加載+防止核心類篡改+安全性
21、GC
Minor GC:回收年輕代
Major GC :回收老年代
Full GC:回收年輕代與老年代,方法區(qū)域
22、內(nèi)存分配與回收策略
「對(duì)象優(yōu)先在Eden區(qū)分配」
大多數(shù)情況下,對(duì)象在新生代 Eden 上分配,當(dāng) Eden 空間不夠時(shí),發(fā)起 Minor GC。
「大對(duì)象直接進(jìn)入老年代」
大對(duì)象指「需要連續(xù)分配空間」的對(duì)象,長(zhǎng)字符串,數(shù)組。
對(duì)象過(guò)大,由于需要連續(xù)的內(nèi)存空間,會(huì)導(dǎo)致提前進(jìn)行垃圾回收以獲取足夠的連續(xù)空間 -XX:PretenureSizeThreshold,大于此值的對(duì)象直接在老年代分配,避免在 Eden 和 Survivor 之間的大量?jī)?nèi)存復(fù)制。
「長(zhǎng)期存活的對(duì)象」
對(duì)象在Eden區(qū)出生,經(jīng)過(guò)一次Minor GC存活下來(lái),分代年齡就會(huì)加一,增加到一定年齡就會(huì)移向老年代。默認(rèn)是15. -XX:MaxTenuringThreshold用來(lái)定義該年齡的閾值。
「動(dòng)態(tài)對(duì)象年齡判定」
虛擬機(jī)并不是永遠(yuǎn)要求對(duì)象的年齡必須達(dá)到MaxTenuringThreshold才能晉升到老年代,當(dāng)「Survivor區(qū)相同年齡的所有對(duì)象大小總和大于Survivork空間一半」,則年齡大于或等于該年齡的對(duì)象直接進(jìn)入老年代,無(wú)需等到 MaxTenuringThreshold 中要求的年齡。
「空間分配擔(dān)保(*)」
在發(fā)生 Minor GC 之前,虛擬機(jī)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果條件成立的話,那么 Minor GC 可以確認(rèn)是安全的。
如果不成立的話虛擬機(jī)會(huì)查看 HandlePromotionFailure 的值是否允許擔(dān)保失敗,如果允許那么就會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小。
如果大于,將嘗試著進(jìn)行一次 Minor GC;如果小于,或者 HandlePromotionFailure 的值不允許冒險(xiǎn),那么就要進(jìn)行一次 Full GC。
23、分代垃圾收集器是怎樣工作的
「分代回收器有兩個(gè)分區(qū)」:老生代和新生代,新生代默認(rèn)的空間占比總空間的 1/3,老生代的默認(rèn)占比是 2/3。
新生代使用的是復(fù)制算法,新生代里有 3 個(gè)分區(qū):Eden、To Survivor、From Survivor,它們的默認(rèn)占比是 8:1:1。
「它的執(zhí)行流程如下:」
把 Eden + From Survivor 存活的對(duì)象放入 To Survivor 區(qū);
清空 Eden 和 From Survivor 分區(qū);
From Survivor 和 To Survivor 分區(qū)交換,F(xiàn)rom Survivor 變 To Survivor,To Survivor 變 From Survivor。
每次在 From Survivor 到 To Survivor 移動(dòng)時(shí)都存活的對(duì)象,年齡就 +1,當(dāng)年齡到達(dá) 15(默認(rèn)配置是 15)時(shí),升級(jí)為老生代。「大對(duì)象也會(huì)直接進(jìn)入老生代?!?/strong>
老生代當(dāng)空間占用到達(dá)某個(gè)值之后就會(huì)觸發(fā)全局垃圾收回,一般使用標(biāo)記整理的執(zhí)行算法。以上這些循環(huán)往復(fù)就構(gòu)成了整個(gè)分代垃圾回收的整體執(zhí)行流程。
24、Full GC觸發(fā)條件
對(duì)于 Minor GC,其觸發(fā)條件非常簡(jiǎn)單,當(dāng) Eden 空間滿時(shí),就將觸發(fā)一次 Minor GC。而 Full GC 則相對(duì)復(fù)雜,有以下條件:
「調(diào)用System.gc()」
只是建議虛擬機(jī)執(zhí)行 Full GC,但是虛擬機(jī)不一定真正去執(zhí)行。不建議使用這種方式,而是讓虛擬機(jī)管理內(nèi)存。
「老年代空間不足」
「場(chǎng)景:」
大對(duì)象直接進(jìn)入老年代(老年代空間足,但是沒(méi)有足夠的連續(xù)空間) 長(zhǎng)期存活的對(duì)象直接進(jìn)入老年代
「解決:」
1、盡量「不要?jiǎng)?chuàng)建過(guò)大的對(duì)象」以及數(shù)組 2、可以通過(guò)-Xmn「調(diào)大新生代大小」,讓「對(duì)象盡量在新生代被回收」,不進(jìn)老年代 3、可以通過(guò)-XX:MaxTenuringThreshold「調(diào)大分代年齡閾值」,讓對(duì)象得新生代多存活一段時(shí)間
「空間分配擔(dān)保失敗」
使用復(fù)制算法的 Minor GC 需要老年代的內(nèi)存空間作擔(dān)保,如果擔(dān)保失敗會(huì)執(zhí)行一次 Full GC。
「解釋一」
老年代最大可用的連續(xù)空間<新生代所有對(duì)象總空間 && HandlerPromotionFailure設(shè)置不允許擔(dān)保失敗 full gc
老年代最大可用的連續(xù)空間>新生代所有對(duì)象總空間 && HandlerPromotionFailure設(shè)置允許擔(dān)保失敗 && 通過(guò)Minor GCJ進(jìn)入老年代的象平均大小>老年代最大連續(xù)空間大小 full gc
「解釋二」
Minor GC前,先判斷老年代最大連續(xù)空間是否大于新生代所有對(duì)象總空間。
若大于,安全;若小于,查看HandlerPromotionFailure設(shè)置是否允許擔(dān)保失敗,允許,再看通過(guò)Minor GCJ進(jìn)入老年代的對(duì)象平均大小>老年代最大連續(xù)
空間太小,則還是失敗,進(jìn)行Full GC。
「jdk1.7以前的永久代空間不足」
永久代可能會(huì)被占滿,在未配置為采用 CMS GC 的情況下也會(huì)執(zhí)行 Full GC。如果經(jīng)過(guò) Full GC 仍然回收不了,那么虛擬機(jī)會(huì)拋出 java.lang.OutOfMemoryError。
為避免以上原因引起的 Full GC,可采用的方法為增大永久代空間或轉(zhuǎn)為使用 CMS GC。
25、為什么有垃圾收集還會(huì)有內(nèi)存泄漏問(wèn)題?
「對(duì)象定義在錯(cuò)誤的范圍」如果長(zhǎng)生命周期的對(duì)象持有短生命周期的引用,就很可能會(huì)出現(xiàn)內(nèi)存泄露。「異常處理不當(dāng)」各種資源的關(guān)閉一定要放在finally里面
26、堆與棧的區(qū)別?
「申請(qǐng)方式」:棧系統(tǒng)自動(dòng)申請(qǐng),堆需手動(dòng)申請(qǐng)c語(yǔ)言malloc(),java new Object();
棧系統(tǒng)分配速度快,堆慢,容易內(nèi)部碎片;棧地址空間連續(xù),堆是不連續(xù)的(鏈表存儲(chǔ)空閑內(nèi)存地址);
「內(nèi)容不一樣」(堆存對(duì)象實(shí)例與數(shù)組,關(guān)注存儲(chǔ);棧存儲(chǔ)局部變量表,操作數(shù)棧,關(guān)注運(yùn)行);
「大下限制」(棧預(yù)先設(shè)定好的,編譯器即可確定,堆取決有效虛擬內(nèi)存,運(yùn)行期間確定)
27、逃逸分析
**概念:**當(dāng)一個(gè)對(duì)象在方法中被定義后,它可能被方法外部其他對(duì)象所引用,則稱逃出方法(內(nèi)存逃逸現(xiàn)象)
使用逃逸分析,編譯器優(yōu)化
同步省略:對(duì)象沒(méi)有方法逃逸,只能被一個(gè)線程訪問(wèn)到,可以不用同步 「將堆分配轉(zhuǎn)為棧分配」
如果JIT經(jīng)過(guò)逃逸分析,發(fā)現(xiàn)有些對(duì)象沒(méi)有逃逸出方法,那么有可能堆內(nèi)存分配會(huì)被優(yōu)化成棧內(nèi)存分配
28、JVM參數(shù)
-Xmx3550:設(shè)置堆最大值 -Xms3660m:設(shè)置初始堆大小 -Xss128k:設(shè)置線程棧大小 -Xmn2g:設(shè)置年輕代大小 -XX:NewSize=1024m:設(shè)置年輕代初始值 -XX:MaxNewSize=1024m:設(shè)置年輕代最大值 -XX:SurvivorRatio=4:設(shè)置Survivor區(qū)與Eden區(qū)比值 -XX:MaxTenuringThreshold=15:設(shè)置分代年齡閾值,滿15就進(jìn)入老年代 -XX:PretenureSizeThreshold:大對(duì)象直接進(jìn)入老年代
29、內(nèi)存持續(xù)上升,我該如何處理
1、啟動(dòng)程序之前通過(guò) HeapDumpOnOutOfMemoryError 和 HeapDumpPath 這兩個(gè)參數(shù)「開啟堆內(nèi)存異常日志」
-XX:+HeapDumpOnOutOfMemoryError?-XX:HeapDumpPath=
2、從日志從發(fā)現(xiàn)異常
3、通過(guò)top命令查看進(jìn)程cpu使用率
4、再通過(guò) top -Hp pid 查看進(jìn)程下所有「具體線程占用系統(tǒng)資源情況」。
5、再通過(guò) jstack pid 查看具體線程的「堆棧信息」(線程ID、狀態(tài)(wait,sleep),是否持有鎖)
6、再通過(guò) jmap 查看「堆內(nèi)存的使用情況」?jmap -heap pid
7、通過(guò)以上命令分析基本可以看出什么問(wèn)題導(dǎo)致內(nèi)存上升,現(xiàn)在分析問(wèn)題產(chǎn)生的原因
8、我們?cè)趩?dòng)時(shí),已經(jīng)設(shè)置了 dump 文件,通過(guò) MAT 打開 dump 的內(nèi)存日志文件,分析即可。
須知
30、JVM性能調(diào)優(yōu)與故障處理
31、基本故障處理工具
32、可視化故障處理工具
最后三節(jié),屬于進(jìn)階內(nèi)容,前面基礎(chǔ)一定要掌握好。由于篇幅有限,關(guān)注公眾號(hào),后期會(huì)專門針對(duì)大廠面試常問(wèn)性能調(diào)優(yōu)與工具進(jìn)行講解。
后記
關(guān)注我公眾號(hào)“小龍coding”,我們一起探討,幫助修改簡(jiǎn)歷,回答疑問(wèn),項(xiàng)目分析,只為幫助迷茫的你高效斬獲心儀offer!
后續(xù)會(huì)陸續(xù)更新大廠面經(jīng)面試題與解析,大廠內(nèi)推「直達(dá)部門主管」,也有交流群大家一起探討共同進(jìn)步。加油噢!
