聊到JVM(還怕面試官問(wèn)JVM嗎?)
點(diǎn)擊上方"程序員歷小冰",選擇“置頂或者星標(biāo)”
? ?你的關(guān)注意義重大!

JVM都是進(jìn)階時(shí)必須邁過(guò)的坎。不管是工作還是面試中,JVM都是必考題。如果不懂JVM的話,薪酬會(huì)非常吃虧(近70%的面試者掛在JVM上了)。請(qǐng)你談?wù)勀銓?duì)JVM的理解?
JVM類加載器是怎么樣的?有幾種?
什么是OOM,什么是StackOverFlowError? 怎么分析?
JVM常用調(diào)優(yōu)參數(shù)有哪寫(xiě)?
GC有幾種算法?分別是怎么執(zhí)行的?
你知道JProfiler嗎,怎么分析Dump文件?

Java虛擬機(jī)!!1、什么是JVM?在哪??
JVM本質(zhì)上是一個(gè)
程序,它能識(shí)別.class?字節(jié)碼文件(里面存放的是我們對(duì).java編譯后產(chǎn)生的二進(jìn)制代碼),并且能夠解析它的指令,最終調(diào)用操作系統(tǒng)上的函數(shù),完成我們想要的操作!關(guān)于Java語(yǔ)言的
跨平臺(tái)性,就是因?yàn)镴VM,我們可以將其想象為一個(gè)抽象層,只要這個(gè)抽象層JVM正確執(zhí)行了.class文件,就能運(yùn)行在各種操作系統(tǒng)之上了!這就是一次編譯,多次運(yùn)行
JVM是運(yùn)行在操作系統(tǒng)之上的,它與硬件沒(méi)有直接的交互

2、JVM、JRE、JDK 的關(guān)系?
JDK = JRE + javac/java/jar 等指令工具
JRE = JVM + Java基本類庫(kù)

3、JVM體系結(jié)構(gòu)?
類裝載器子系統(tǒng)
運(yùn)行時(shí)數(shù)據(jù)區(qū)
執(zhí)行引擎
本地方法接口
垃圾收集模塊
????
方法區(qū)是一種特殊的堆
棧里面不會(huì)有垃圾,用完就彈出了,否則阻塞了main方法
垃圾幾乎都在堆里,所以JVM性能調(diào)優(yōu)%99都針對(duì)于堆
4、三種JVM(了解)?
HotSpot(我們都用的這個(gè))
JRockitJ9 VM5、類加載器??
.Class字節(jié)碼文件1、回顧new對(duì)象的過(guò)程
public?class?Student?{
????//私有屬性
????private?String?name;
????//構(gòu)造方法
????public?Student(String?name)?{
????????this.name?=?name;
????}
}
//運(yùn)行時(shí),JVM將Test的信息放入方法區(qū)
public?class?Test{
????//main方法本身放入方法區(qū)
??public?static?void?main(String[]?args){
????????//s1、s2、s3為不同對(duì)象
????????Student?s1?=?new?Student("zsr");??//引用放在棧里,具體的實(shí)例放在堆里
????????Student?s2?=?new?Student("gcc");
????????Student?s3?=?new?Student("BareTH");
????????System.out.println(s1.hashCode());
????????System.out.println(s2.hashCode());
????????System.out.println(s3.hashCode());
????????//class1、class2、class3為同一個(gè)對(duì)象
????????Class?extends?Student>?class1?=?s1.getClass();
????????Class?extends?Student>?class2?=?s2.getClass();
????????Class?extends?Student>?class3?=?s3.getClass();
????????System.out.println(class1.hashCode());
????????System.out.println(class2.hashCode());
????????System.out.println(class3.hashCode());
????}
}
s1、s2、s3的hashcode是不同的,因?yàn)槭侨齻€(gè)不同的對(duì)象,對(duì)象是具體的
class1、class2、class3的hashcode是相同的,因?yàn)檫@是類模板,模板是抽象的
????
首先Class Loader讀取字節(jié)碼
.class文件,加載初始化生成Student模板類通過(guò)
Student模板類new出三個(gè)對(duì)象

那么Class Loader具體是怎么執(zhí)行我們的.class字節(jié)碼文件呢,這就引出了我們類加載器~
2、類加載器的類別
我們編寫(xiě)這樣一個(gè)程序

c++編寫(xiě),加載java核心庫(kù)?java.*,構(gòu)造拓展類加載器和應(yīng)用程序加載器。根加載器加載拓展類加載器,并且將拓展類加載器的父加載器設(shè)置為根加載器,然后再加載
應(yīng)用程序加載器,應(yīng)將應(yīng)用程序加載器的父加載器設(shè)置為拓展類加載器由于引導(dǎo)類加載器涉及到虛擬機(jī)本地實(shí)現(xiàn)細(xì)節(jié),我們無(wú)法直接獲取到啟動(dòng)類加載器的引用;這就是上面那個(gè)程序我們第三個(gè)結(jié)果為
null的原因。加載文件存在位置

java編寫(xiě),加載擴(kuò)展庫(kù),開(kāi)發(fā)者可以直接使用標(biāo)準(zhǔn)擴(kuò)展類加載器。java9之前為
ExtClassloader,Java9以后改名為PlatformClassLoader? ??加載文件存在位置

java編寫(xiě),加載程序所在的目錄????2. 是Java默認(rèn)的類加載器
java編寫(xiě),用戶自定義的類加載器,可加載指定路徑的class文件6、雙親委派機(jī)制?
1、什么是雙親委派機(jī)制
類加載器收到類加載的請(qǐng)求
將這個(gè)請(qǐng)求向上委托給父類加載器去完成,一直向上委托,直到根加載器
BootstrapClassLoader根加載器檢查是否能夠加載當(dāng)前類,能加載就結(jié)束,使用當(dāng)前的加載器;否則就拋出異常,通知子加載器進(jìn)行加載;自加載器重復(fù)該步驟。
????
2、作用
舉個(gè)例子:我們重寫(xiě)以下java.lang包下的String類

雙親委派機(jī)制起的作用,當(dāng)類加載器委托到根加載器的時(shí)候,String類已經(jīng)被根加載器加載過(guò)一遍了,所以不會(huì)再加載,從一定程度上防止了危險(xiǎn)代碼的植入!!- 1.? 防止重復(fù)加載同一個(gè)
.class。通過(guò)不斷委托父加載器直到根加載器,如果父加載器加載過(guò)了,就不用再加載一遍。保證數(shù)據(jù)安全。2. 保證系統(tǒng)核心.class,如上述的String類不能被篡改。通過(guò)委托方式,不會(huì)去篡改核心.class,即使篡改也不會(huì)去加載,即使加載也不會(huì)是同一個(gè).class對(duì)象了。不同的加載器加載同一個(gè).class也不是同一個(gè)class對(duì)象。這樣保證了class執(zhí)行安全。
7、沙箱安全機(jī)制?
什么是沙箱?
沙箱(sandbox)1. 沙箱是一個(gè)限制程序運(yùn)行的環(huán)境。沙箱機(jī)制就是將 Java 代碼限定在虛擬機(jī)(JVM)特定的運(yùn)行范圍中,并且嚴(yán)格限制代碼對(duì)本地系統(tǒng)資源訪問(wèn),通過(guò)這樣的措施來(lái)保證對(duì)代碼的有效隔離,防止對(duì)本地系統(tǒng)造成破壞。
沙箱主要限制系統(tǒng)資源訪問(wèn),系統(tǒng)資源包括CPU、內(nèi)存、文件系統(tǒng)、網(wǎng)絡(luò)。不同級(jí)別的沙箱對(duì)這些資源訪問(wèn)的限制也可以不一樣。
java中的安全模型演進(jìn)
本地代碼和遠(yuǎn)程代碼兩種本地代碼
可信任,可以訪問(wèn)一切本地資源。遠(yuǎn)程代碼
不可信信在早期的Java實(shí)現(xiàn)中,安全依賴于沙箱 (Sandbox) 機(jī)制。

Java1.1?版本中,針對(duì)安全機(jī)制做了改進(jìn),增加了安全策略,允許用戶指定代碼對(duì)本地資源的訪問(wèn)權(quán)限。
Java1.2版本中,再次改進(jìn)了安全機(jī)制,增加了代碼簽名。不論本地代碼或是遠(yuǎn)程代碼,都會(huì)按照用戶的安全策略設(shè)定,由類加載器加載到虛擬機(jī)中權(quán)限不同的運(yùn)行空間,來(lái)實(shí)現(xiàn)差異化的代碼執(zhí)行權(quán)限控制。

域 (Domain)?的概念。虛擬機(jī)會(huì)把所有代碼加載到不同的
系統(tǒng)域和應(yīng)用域系統(tǒng)域部分專門負(fù)責(zé)與關(guān)鍵資源進(jìn)行交互應(yīng)用域部分則通過(guò)系統(tǒng)域的部分代理來(lái)對(duì)各種需要的資源進(jìn)行訪問(wèn)。虛擬機(jī)中不同的受保護(hù)域 (Protected Domain),對(duì)應(yīng)不一樣的權(quán)限 (Permission)。存在于不同域中的類文件就具有了當(dāng)前域的全部權(quán)限,如下圖所示

組成沙箱的基本組件
1.?字節(jié)碼校驗(yàn)器(bytecode verifier)
2.?類裝載器(class loader)
它防止惡意代碼去干涉善意的代碼;
它守護(hù)了被信任的類庫(kù)邊界;
它將代碼歸入保護(hù)域,確定了代碼可以進(jìn)行哪些操作。
從最內(nèi)層JVM自帶類加載器開(kāi)始加載,外層惡意同名類得不到加載從而無(wú)法使用;
由于嚴(yán)格通過(guò)包來(lái)區(qū)分了訪問(wèn)域,外層惡意的類通過(guò)內(nèi)置代碼也無(wú)法獲得權(quán)限訪問(wèn)到內(nèi)層類,破壞代碼就自然無(wú)法生效。
存取控制器(access controller):存取控制器可以控制核心API對(duì)操作系統(tǒng)的存取權(quán)限,而這個(gè)控制的策略設(shè)定,可以由用戶指定。安全管理器(security manager):是核心API和操作系統(tǒng)之間的主要接口。實(shí)現(xiàn)權(quán)限控制,比存取控制器優(yōu)先級(jí)高。安全軟件包(security package):java.security下的類和擴(kuò)展包下的類,允許用戶為自己的應(yīng)用增加新的安全特性,包括:安全提供者
消息摘要
數(shù)字簽名
加密
鑒別
8、Native本地方法接口?
JNI:Java Native Interface
本地接口的作用是融合不同的編程語(yǔ)言為Java所用,它的初衷是融合C/C++程序

native:凡是帶native關(guān)鍵字的,說(shuō)明java的作用范圍達(dá)不到了,會(huì)去調(diào)用底層c語(yǔ)言的庫(kù)!進(jìn)入本地方法棧,調(diào)用本地方法接口JNI,拓展Java的使用,融合不同的語(yǔ)言為Java所用Java誕生的時(shí)候C、C++橫行,為了立足,必須要能調(diào)用C、C++的程序
于是在內(nèi)存區(qū)域中專門開(kāi)辟了一塊標(biāo)記區(qū)域:Native Method Stack,登記Native方法
最終在執(zhí)行引擎執(zhí)行的的時(shí)候通過(guò)JNI(本地方法接口)加載本地方法庫(kù)的方法
9、PC寄存器??
程序計(jì)數(shù)器:Program Counter Register每個(gè)線程都有一個(gè)程序計(jì)數(shù)器,是
線程私有的,就是一個(gè)指針,指向方法區(qū)中的方法字節(jié)碼(用來(lái)存儲(chǔ)指向像一條指令的地址,也即將要執(zhí)行的指令代碼),在執(zhí)行引擎讀取下一條指令,是一個(gè)非常小的內(nèi)存空間,幾乎可以忽略不計(jì)
10、方法區(qū)?
方法區(qū):Method Area方法區(qū)是被所有線程共享,所有字段和方法字節(jié)碼,以及一些特殊方法,如構(gòu)造函數(shù),接口代碼也在此定義,簡(jiǎn)單說(shuō),所有定義的方法的信息都保存在該區(qū)域,此區(qū)域?qū)儆?/span>
共享區(qū)間;方法區(qū)與Java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。雖然Java 虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是它卻有一個(gè)別名叫做
Non-Heap(非堆),目的應(yīng)該是與Java 堆區(qū)分開(kāi)來(lái)。
1. 方法區(qū)中有啥?
靜態(tài)變量(static)
常量(final)
類信息(構(gòu)造方法、接口定義)
運(yùn)行時(shí)的
常量池
2. 創(chuàng)建對(duì)象內(nèi)存分析


創(chuàng)建一個(gè)對(duì)象時(shí),方法區(qū)中會(huì)生成對(duì)應(yīng)類的抽象模板;還有對(duì)應(yīng)的常量池、靜態(tài)變量、類信息、常量
我們通過(guò)類模板去new對(duì)象的時(shí)候
堆中存放實(shí)例對(duì)象
棧中存放對(duì)象的引用,每個(gè)對(duì)象對(duì)應(yīng)一個(gè)地址指向堆中相同地址的實(shí)例對(duì)象
11、棧??
棧內(nèi)存,主管程序的運(yùn)行,生命周期和線程同步,線程結(jié)束,棧內(nèi)存就釋放了,不存在垃圾回收棧:先進(jìn)后出
隊(duì)列:先進(jìn)先出(FIFO)
1、棧中存放啥?
8大基本類型
對(duì)象引用
實(shí)例的方法
2、棧運(yùn)行原理
棧表示Java方法執(zhí)行的內(nèi)存模型
每調(diào)用一個(gè)方法就會(huì)為每個(gè)方法生成一個(gè)
棧幀(Stack Frame),每個(gè)方法被調(diào)用和完成的過(guò)程,都對(duì)應(yīng)一個(gè)棧幀從虛擬機(jī)棧上入棧和出棧的過(guò)程。程序正在執(zhí)行的方法一定在棧的頂部

3、堆棧溢出StackOverflowError
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????new?Test().a();
????}
????public?void?a()?{
????????b();
????}
????public?void?b()?{
????????a();
????}
}


12、堆??
Heap,一個(gè)JVM只有一個(gè)堆內(nèi)存(棧是線程級(jí)的),堆內(nèi)存的大小是可以調(diào)節(jié)的

1、堆中有啥?
2、堆內(nèi)存詳解

1、Young 年輕代
對(duì)象誕生、成長(zhǎng)甚至死亡的區(qū)
Eden Space(伊甸園區(qū)):所有的對(duì)象都是在此new出來(lái)的Survivor Space(幸存區(qū))
幸存0區(qū)(From Space)(動(dòng)態(tài)的,F(xiàn)rom和To會(huì)互相交換)幸存1區(qū)(To Space)
Eden區(qū)占大容量,Survivor兩個(gè)區(qū)占小容量,默認(rèn)比例是8:1:1。
2、Tenured 老年代
3、Perm 元空間
存儲(chǔ)的是Java運(yùn)行時(shí)的一些環(huán)境或類信息,這個(gè)區(qū)域不存在垃圾回收!關(guān)閉虛擬機(jī)就會(huì)釋放這個(gè)區(qū)域內(nèi)存!
這個(gè)區(qū)域常駐內(nèi)存,用來(lái)存放JDK自身攜帶的Class對(duì)象、Interface元數(shù)據(jù)。
jdk1.6之前:
永久代jdk1.7:
永久代慢慢退化,去永久代jdk1.8之后:
永久代改名為元空間

3、什么是OOM?
內(nèi)存溢出java.lang.OutOfMemoryError
分配的太少
用的太多
用完沒(méi)釋放
4、GC垃圾回收
GC垃圾回收,主要在年輕代和老年代
伊甸園區(qū)假設(shè)
伊甸園區(qū)只能存一定數(shù)量的對(duì)象,則每當(dāng)存滿時(shí)就會(huì)觸發(fā)一次輕GC(Minor GC)輕GC清理后,有的對(duì)象可能還存在引用,就活下來(lái)了,活下來(lái)的對(duì)象就進(jìn)入幸存區(qū);有的對(duì)象沒(méi)用了,就被GC清理掉了;每次輕GC都會(huì)使得伊甸園區(qū)為空如果
幸存區(qū)和伊甸園都滿了,則會(huì)進(jìn)入老年代,如果老年代滿了,就會(huì)觸發(fā)一次重GC(FullGC),年輕代+老年代的對(duì)象都會(huì)清理一次,活下的對(duì)象就進(jìn)入老年代如果
新生代和老年代都滿了,則OOM
OOM13、堆內(nèi)存調(diào)優(yōu)?
1、查看并設(shè)置JVM堆內(nèi)存
堆內(nèi)存public?class?Test?{
????public?static?void?main(String[]?args)?{
????????//返回jvm試圖使用的最大內(nèi)存
????????long?max?=?Runtime.getRuntime().maxMemory();
????????//返回jvm的初始化內(nèi)存
????????long?total?=?Runtime.getRuntime().totalMemory();
????????//默認(rèn)情況下:分配的總內(nèi)存為電腦內(nèi)存的1/4,初始化內(nèi)存為電腦內(nèi)存的1/64
????????System.out.println("max="?+?max?/?(double)?1024?/?1024?/?1024?+?"G");
????????System.out.println("total="?+?total?/?(double)?1024?/?1024?/?1024?+?"G");
????}
}


JVM最大分配內(nèi)存為電腦內(nèi)存的1/4
JVM初始化內(nèi)存為電腦內(nèi)存的1/64

VM options中可以指定jvm試圖使用的最大內(nèi)存和jvm初始化內(nèi)存大小-Xms1024m -Xmx1024m -Xlog:gc*
-Xmx用來(lái)設(shè)置jvm試圖使用的最大內(nèi)存,默認(rèn)為1/4-Xms用來(lái)設(shè)置jvm初始化內(nèi)存,默認(rèn)為1/64-Xlog:gc*用來(lái)打印GC垃圾回收信息


2、怎么排除OOM錯(cuò)誤?
1. 嘗試擴(kuò)大堆內(nèi)存看結(jié)果
jvm試圖使用的最大內(nèi)存和jvm初始化內(nèi)存大小2. 利用內(nèi)存快照工具JProfiler
MAT(Eclipse)
JProfiler
分析Dump內(nèi)存文件,快速定位內(nèi)存泄漏
獲得堆中的文件
獲得大的對(duì)象
…
3. 什么是Dump文件?如何分析?
進(jìn)程的內(nèi)存鏡像,可以把程序的執(zhí)行狀態(tài)通過(guò)調(diào)試器保存到dump文件中import?java.util.ArrayList;
public?class?Test?{
????byte[]?array?=?new?byte[1024?*?1024];//1M
????public?static?void?main(String[]?args)?{
????????ArrayList?list?=?new?ArrayList<>();
????????int?count?=?0;
????????try?{
????????????while?(true)?{
????????????????list.add(new?Test());
????????????????count++;
????????????}
????????}?catch?(Exception?e)?{
????????????System.out.println("count="?+?count);
????????????e.printStackTrace();
????????}
????}
}
OOM
接下來(lái)我們?cè)O(shè)置以下堆內(nèi)存,并附加生成對(duì)應(yīng)的dump文件的指令
-Xms1m?-Xmx8m?-XX:+HeapDumpOnOutOfMemoryError-XX:+HeapDumpOnOutOfMemoryError表示當(dāng)JVM發(fā)生OOM時(shí),自動(dòng)生成DUMP文件。再次點(diǎn)擊運(yùn)行,下載了對(duì)應(yīng)的Dump文件

Show in Explorer
一直點(diǎn)擊上級(jí)目錄,直到找到.hprof文件,與src同級(jí)目錄下

我們雙擊打開(kāi),可以看到每塊所占的大小,便于分析問(wèn)題

點(diǎn)擊Thread Dump,里面是所有的線程,點(diǎn)擊對(duì)應(yīng)的線程可以看到相應(yīng)的錯(cuò)誤,反饋到具體的行,便于排錯(cuò)

每次打開(kāi)Dump文件查看完后,建議刪除,可以在idea中看到,打開(kāi)文件后生成了很多內(nèi)容,占內(nèi)存,建議刪除

附:安裝Jprofiler教程

2.下載客戶端 https://www.ej-technologies.com/download/jprofiler/files






?打開(kāi)IDEA的設(shè)置,找到Tools里面的JProfiler,沒(méi)有設(shè)置位置則設(shè)置位置

此時(shí)則全部安裝完成!
14、GC垃圾回收?
1、回顧
Garbage Collection:垃圾回收

JVM在進(jìn)行GC時(shí),并不是對(duì)
年輕代、老年代統(tǒng)一回收;大部分時(shí)候,回收都是在年輕代GC分為兩種:
輕GC(清理年輕代)
重GC(清理年輕代+老年代)
2、GC算法
1、引用計(jì)數(shù)算法(很少使用)
每個(gè)對(duì)象在創(chuàng)建的時(shí)候,就給這個(gè)對(duì)象綁定一個(gè)計(jì)數(shù)器。
每當(dāng)有一個(gè)引用指向該對(duì)象時(shí),計(jì)數(shù)器加一;每當(dāng)有一個(gè)指向它的引用被刪除時(shí),計(jì)數(shù)器減一。
這樣,當(dāng)沒(méi)有引用指向該對(duì)象時(shí),該對(duì)象死亡,計(jì)數(shù)器為0,這時(shí)就應(yīng)該對(duì)這個(gè)對(duì)象進(jìn)行垃圾回收操作。

2、復(fù)制算法
年輕代(?幸存0區(qū)?和?幸存1區(qū))當(dāng)Eden區(qū)滿的時(shí)候,會(huì)觸發(fā)
輕GC,每觸發(fā)一次,活的對(duì)象就被轉(zhuǎn)移到幸存區(qū),死的就被GC清理掉了,所以每觸發(fā)輕GC時(shí),Eden區(qū)就會(huì)清空;對(duì)象被轉(zhuǎn)移到了幸存區(qū),幸存區(qū)又分為
From Space和To Space,這兩塊區(qū)域是動(dòng)態(tài)交換的,誰(shuí)是空的誰(shuí)就是To Space,然后From Space就會(huì)把全部對(duì)象轉(zhuǎn)移到To Space去;那如果兩塊區(qū)域都不為空呢?這就用到了
復(fù)制算法,其中一個(gè)區(qū)域會(huì)將存活的對(duì)象轉(zhuǎn)移到令一個(gè)區(qū)域去,然后將自己區(qū)域的內(nèi)存空間清空,這樣該區(qū)域?yàn)榭眨殖蔀榱?/span>To Space;所以每次觸發(fā)
輕GC后,Eden區(qū)清空,同時(shí)To區(qū)也清空了,所有的對(duì)象都在From區(qū)
這也就是幸存0區(qū)和幸存1區(qū)總有一塊為空的原因

浪費(fèi)了內(nèi)存空間(浪費(fèi)了幸存區(qū)一半空間)
對(duì)象存活率較高的場(chǎng)景下(比如老年代那樣的環(huán)境),需要復(fù)制的東西太多,效率會(huì)下降。
年輕代3、標(biāo)記–清除算法
標(biāo)記階段:這個(gè)階段內(nèi),為每個(gè)對(duì)象更新標(biāo)記位,檢查對(duì)象是否死亡;
清除階段:該階段對(duì)死亡的對(duì)象進(jìn)行清除,執(zhí)行 GC 操作。

4、標(biāo)記–整理算法
標(biāo)記-整理法?是?標(biāo)記-清除法?的一個(gè)改進(jìn)版。標(biāo)記-清楚-壓縮法標(biāo)記階段,該算法也將所有對(duì)象標(biāo)記為存活和死亡兩種狀態(tài);
不同的是,在第二個(gè)階段,該算法并沒(méi)有直接對(duì)死亡的對(duì)象進(jìn)行清理,而是將所有存活的對(duì)象整理一下,放到另一處空間,然后把剩下的所有對(duì)象全部清除。

標(biāo)記清除,到達(dá)一定量的時(shí)候再壓縮.總結(jié)
思考:有沒(méi)有最優(yōu)的算法?
分代收集算法對(duì)象存活率低
用復(fù)制算法
區(qū)域大,對(duì)象存活率高
用
標(biāo)記清除+標(biāo)記壓縮混合實(shí)現(xiàn)

結(jié)束!
????
????作者:?Baret H
????原文鏈接:http://i8n.cn/iWLG4r
-關(guān)注我
