不詩意的女程序媛不是好廚師~ 轉(zhuǎn)載請注明出處款熬,F(xiàn)rom李詩雨---[https://blog.csdn.net/cjm2484836553/article/details/103528907]
前言:Jvm是啥
圖可以大致理解為:
JDK - 類庫 = JRE
JRE - API --> JVM(翻譯)
【JVM是啥】
-
其實 JVM 就是 翻譯 .
字節(jié)碼 --> JVM(翻譯) ---> 機器碼(讓電腦的CPU可以直接讀鹊刻丁)</pre>
- JVM不單單只支持Java語言坟冲,也支持其他語言(Scala、Kotlin邪蛔、Groovy等等)
【虛擬機歷史】
-
目前使用范圍最廣的Java虛擬機---HotSpot VM
Hotspot什么意思:熱點代碼探測技術(shù)急黎,及時編譯器(發(fā)現(xiàn)最有價值的代碼,如果代碼用得非常多侧到,就會把這些代碼編譯成本地代碼)勃教。
谷歌(谷歌主要開發(fā)語言也是Java):Google Android Dalivk VM
1.運行時數(shù)據(jù)區(qū)
一個Class文件經(jīng)過類加載了,然后它就會被放到運行時數(shù)據(jù)區(qū)里面匠抗。
-
那運行時數(shù)據(jù)區(qū) 是什么呢故源?
Java虛擬機在執(zhí)行Java程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域
注意:JVM所有的東西都是放在內(nèi)存的
運行時數(shù)據(jù)區(qū) 按照線程私有 和 線程共享 進行劃分(如下圖所示)。
線程私有---即:你單獨起一個線程這里就會單獨有一塊東西
?
線程共享---即:無論你起多少個線程汞贸,這個區(qū)域就一份</pre>
2.站在線程角度來看
2.1 線程私有
2.1.1程序計數(shù)器
【定義】:指向當前線程正在執(zhí)行的字節(jié)碼指令(Class)的地址(行號)
注意:jvm是運行class的绳军,不是運行java的
【舉個栗子】:
比如大家都有上過公開課的經(jīng)歷,
老師在上課我們可以看做線程1矢腻;
老師有時候會與我們互動门驾,解決我們的疑問,我們看做線程2.
那老師暫停講課多柑,和我們互動的時候奶是,他要記住自己講到哪里了,以便后面繼續(xù)講課竣灌,而不是從頭再來講诫隅。
? 而程序計數(shù)器就是起到了記錄的作用,以保證2個線程在切換過程中帐偎,程序仍然能正常執(zhí)行。
-
程序計數(shù)器占用的內(nèi)存非常非常的小蛔屹,因為它就記一個數(shù)削樊。
是當前線程執(zhí)行的字節(jié)碼的行號指示器;
各線程之間獨立存儲,互不影響
-
(面試)為什么需要程序計數(shù)器
? Java是多線程的漫贞,意味著線程切換
? 確保多線程情況下的程序正常執(zhí)行
在說虛擬機棧之前甸箱,我們先來提一下棧~
穿插提一下→棧
-
棧(Stack):數(shù)據(jù)結(jié)構(gòu)
入口和出口只有一個
入棧
出棧
-
特點
先進后出(FIL0)
-
為什么JVM要使用棧?
非常符合JAVA中方法間的調(diào)用
先入棧的方法迅脐,最后出來芍殖。
好下面我們來看看虛擬機棧:
2.1.2虛擬機棧
-
虛擬機棧:
每個線程私有的,線程在運行時谴蔑,在執(zhí)行每個方法的時候都會打包成一個棧幀豌骏,
棧幀中 存儲了局部變量表,操作數(shù)棧隐锭,動態(tài)鏈接窃躲,方法出口等信息,然后放入棧钦睡。
每個時刻正在執(zhí)行的當前方法就是虛擬機棧頂?shù)臈E蒂窒。
方法的執(zhí)行就對應著棧幀在虛擬機棧中入棧和出棧的過程。
棧的大小缺省為1M荞怒,
改虛擬棧的大腥髯痢:可用參數(shù) –Xss調(diào)整大小,例如-Xss256k
下面我們來一一講解棧幀的內(nèi)部:
(1) 局部變量表
-
定義:顧名思義就是局部變量的表褐桌,用于存放我們的局部變量的衰抑。
首先它是一個32位的長度,主要存放我們的Java的八大基礎(chǔ)數(shù)據(jù)類型撩嚼,一般32位就可以存放下停士,如果是64位的就使用高低位占用兩個也可以存放下,如果是局部的一些對象完丽,比如我們的Object對象恋技,我們只需要存放它的一個引用地址即可。
局部變量表的第0個位置始終放的都是this這個對象逻族,放的是引用蜻底。
(2)操作數(shù)棧
-
定義:存放我們方法執(zhí)行的操作數(shù)的,它就是一個棧聘鳞,先進后出的棧結(jié)構(gòu)薄辅。
操作數(shù)棧,就是用來操作的抠璃,操作的的元素可以是任意的java數(shù)據(jù)類型站楚。
我們知道一個方法剛剛開始的時候,這個方法的操作數(shù)棧就是空的搏嗡,操作數(shù)棧運行方法是會一直進行入棧/出棧的操作
(3)返回地址
正常返回(調(diào)用程序計數(shù)器中的地址作為返回)窿春、異常的話(通過異常處理器表<非棧幀中的>來確定)
(4)動態(tài)鏈接
動態(tài)鏈接: Java語言特性多態(tài)(需要類加載拉一、運行時才能確定具體的方法),動態(tài)特性(Groovy旧乞、JS蔚润、動態(tài)代理)
2.1.3本地方法棧
本地方法棧保存的是native方法的信息
各虛擬機自由實現(xiàn)。
當一個JVM創(chuàng)建的線程調(diào)用native方法后尺栖,JVM不再為其在虛擬機棧中創(chuàng)建棧幀嫡纠,JVM只是簡單地動態(tài)鏈接并直接調(diào)用native方法
2.2線程共享
2.2.1方法區(qū)
方法區(qū)中都存儲了哪些信息呢?
類信息 (即class)
常量 ("lsy","123"等)
靜態(tài)變量 (static變量)
-
即時編譯期編譯后的代碼
這個指的是動態(tài)即使編譯延赌,我在運行的時候才進行編譯除盏。編譯完之后也是放方法區(qū)。
2.2.2Java堆
哪些東西都在方法堆中呢皮胡?
-
對象實例(幾乎所有)
為什么說是幾乎所有對象痴颊?→ 后面會講 棧分配對象
數(shù)組
Java堆也是垃圾回收發(fā)生的主要區(qū)域,
Java堆的大小參數(shù)設(shè)置
-Xmx 堆區(qū)內(nèi)存可被分配的最大上限
-Xms 堆區(qū)內(nèi)存初始內(nèi)存分配的大小
3.直接內(nèi)存(了解即可)
直接內(nèi)存不是虛擬機運行時數(shù)據(jù)區(qū)的一部分屡贺,也不是java虛擬機規(guī)范中定義的內(nèi)存區(qū)域蠢棱;
如果使用了NIO,這塊區(qū)域會被頻繁使用,在java堆內(nèi)可以用directByteBuffer對象直接引用并操作甩栈;
這塊內(nèi)存不受java堆大小限制泻仙,但受本機總內(nèi)存的限制,
可以通過-XX:MaxDirectMemorySize來設(shè)置(默認與堆內(nèi)存最大值一樣)量没,所以也會出現(xiàn)OOM異常玉转。
4.深入辨析堆和棧
功能
? 以棧幀的方式存儲方法調(diào)用的過程,并存儲方法調(diào)用過程中基本數(shù)據(jù)類型的變量(int殴蹄、short究抓、long、byte袭灯、float刺下、double、boolean稽荧、char等)以及對象的引用變量橘茉,其內(nèi)存分配在棧上,變量出了作用域就會自動釋放姨丈;
? 而堆內(nèi)存用來存儲Java中的對象畅卓。無論是成員變量,局部變量蟋恬,還是類變量翁潘,它們指向的對象都存儲在堆內(nèi)存中;
線程獨享還是共享
? 棧內(nèi)存歸屬于單個線程歼争,每個線程都會有一個棧內(nèi)存唐础,其存儲的變量只能在其所屬線程中可見箱歧,即棧內(nèi)存 可以理解成線程的私有內(nèi)存。
? 堆內(nèi)存中的對象對所有線程可見一膨。堆內(nèi)存中的對象可以被所有線程訪問
空間大小
? 棧的內(nèi)存要遠遠小于堆內(nèi)存,棧的深度是有限制的洒沦,可能發(fā)生StackOverFlowError問題豹绪。
(虛擬機棧可以理解為最大為1M申眼,那100個線程-->100M)
5.深入理解對象
5.1虛擬機角度來看瞒津,一個對象的誕生
當虛擬機遇到一條new指令時,會經(jīng)歷如下過程:
1.檢查加載
先執(zhí)行相應的類加載過程括尸。
2.分配內(nèi)存
接下來虛擬機將為新生對象分配內(nèi)存巷蚪。為對象分配空間的任務等同于把一塊確定大小的內(nèi)存從Java堆中劃分出來。
- 指針碰撞 濒翻、空閑列表
如果Java堆中內(nèi)存是絕對規(guī)整的屁柏,所有用過的內(nèi)存都放在一邊,空閑的內(nèi)存放在另一邊有送,中間放著一個指針作為分界點的指示器淌喻,那所分配內(nèi)存就僅僅是把那個指針向空閑空間那邊挪動一段與對象大小相等的距離,這種分配方式稱為“指針碰撞”雀摘。
如果Java堆中的內(nèi)存并不是規(guī)整的裸删,已使用的內(nèi)存和空閑的內(nèi)存相互交錯,那就沒有辦法簡單地進行指針碰撞了阵赠,虛擬機就必須維護一個列表涯塔,記錄上哪些內(nèi)存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例清蚀,并更新列表上的記錄匕荸,這種分配方式稱為“空閑列表”。
選擇哪種分配方式由Java堆是否規(guī)整決定轧铁,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定每聪。
空閑列表的產(chǎn)生和垃圾回收有一定關(guān)系,因為只有發(fā)生垃圾回收才會出現(xiàn)這種空的不連續(xù)齿风。
-
并發(fā)安全
除如何劃分可用空間之外药薯,還有另外一個需要考慮的問題是對象創(chuàng)建在虛擬機中是非常頻繁的行為,即使是僅僅修改一個指針所指向的位置救斑,在并發(fā)情況下也并不是線程安全的童本,可能出現(xiàn)正在給對象A分配內(nèi)存,指針還沒來得及修改脸候,對象B又同時使用了原來的指針來分配內(nèi)存的情況穷娱。
? CAS機制:
解決這個問題有兩種方案绑蔫,一種是對分配內(nèi)存空間的動作進行同步處理——實際上虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性;
注意一下:A-B-A 即不關(guān)心中間過程泵额,最終還是你的就行配深。
另一種是把內(nèi)存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在Java堆中預先分配一小塊私有內(nèi)存嫁盲,也就是本地線程分配緩沖(Thread Local Allocation Buffer,TLAB)篓叶,如果設(shè)置了虛擬機參數(shù) -XX:UseTLAB,在線程初始化時羞秤,同時也會申請一塊指定大小的內(nèi)存缸托,只給當前線程使用,這樣每個線程都單獨擁有一個Buffer瘾蛋,如果需要分配內(nèi)存俐镐,就在自己的Buffer上分配,這樣就不存在競爭的情況哺哼,可以大大提升分配效率佩抹,當Buffer容量不夠的時候,再重新從Eden區(qū)域申請一塊繼續(xù)使用幸斥。
TLAB的目的是在為新對象分配內(nèi)存空間時匹摇,讓每個Java應用線程能在使用自己專屬的分配指針來分配空間,減少同步開銷甲葬。
TLAB只是讓每個線程有私有的分配指針廊勃,但底下存對象的內(nèi)存空間還是給所有線程訪問的,只是其它線程無法在這個區(qū)域分配而已经窖。當一個TLAB用滿(分配指針top撞上分配極限end了)坡垫,就新申請一個TLAB。
3.內(nèi)存空間初始化
(注意不是構(gòu)造方法)內(nèi)存分配完成后画侣,虛擬機需要將分配到的內(nèi)存空間都初始化為零值(如int值為0冰悠,boolean值為false等等)。這一步操作保證了對象的實例字段在Java代碼中可以不賦初始值就直接使用配乱,程序能訪問到這些字段的數(shù)據(jù)類型所對應的零值溉卓。
4.設(shè)置
接下來,虛擬機要對對象進行必要的設(shè)置搬泥,例如這個對象是哪個類的實例桑寨、如何才能找到類的元數(shù)據(jù)信息、對象的哈希碼忿檩、對象的GC分代年齡等信息尉尾。這些信息存放在對象的對象頭之中。
5.對象初始化
在上面工作都完成之后燥透,從虛擬機的視角來看沙咏,一個新的對象已經(jīng)產(chǎn)生了辨图,但從Java程序的視角來看,對象創(chuàng)建才剛剛開始肢藐,所有的字段都還為零值故河。所以,一般來說吆豹,執(zhí)行new指令之后會接著把對象按照程序員的意愿進行初始化忧勿,這樣一個真正可用的對象才算完全產(chǎn)生出來。
5.2對象的內(nèi)存布局
在HotSpot虛擬機中瞻讽,對象在內(nèi)存中存儲的布局可以分為3塊區(qū)域:對象頭(Header)、實例數(shù)據(jù)(Instance Data)和對齊填充(Padding)熏挎。
對象頭包括兩部分信息速勇,第一部分用于存儲對象自身的運行時數(shù)據(jù),如哈希碼(HashCode)坎拐、GC分代年齡烦磁、鎖狀態(tài)標志、線程持有的鎖哼勇、偏向線程ID都伪、偏向時間戳等。
對象頭的另外一部分是類型指針积担,即對象指向它的類元數(shù)據(jù)的指針陨晶,虛擬機通過這個指針來確定這個對象是哪個類的實例。
第三部分對齊填充并不是必然存在的帝璧,也沒有特別的含義先誉,它僅僅起著占位符的作用。由于HotSpot VM的自動內(nèi)存管理系統(tǒng)要求對對象的大小必須是8字節(jié)的整數(shù)倍的烁。當對象其他數(shù)據(jù)部分沒有對齊時褐耳,就需要通過對齊填充來補全。
5.3對象的訪問定位
-
句柄
如果使用句柄訪問的話渴庆,那么Java堆中將會劃分出一塊內(nèi)存來作為句柄池铃芦,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息襟雷。
-
直接指針
如果使用直接指針訪問刃滓, reference中存儲的直接就是對象地址。
這兩種對象訪問方式各有優(yōu)勢嗤军,使用句柄來訪問的最大好處就是reference中存儲的是穩(wěn)定的句柄地址注盈,在對象被移動(垃圾收集時移動對象是非常普遍的行為)時只會改變句柄中的實例數(shù)據(jù)指針,而reference本身不需要修改叙赚。
使用直接指針訪問方式的最大好處就是速度更快老客,它節(jié)省了一次指針定位的時間開銷僚饭,由于對象的訪問在Java中非常頻繁,因此這類開銷積少成多后也是一項非畴逝椋可觀的執(zhí)行成本鳍鸵。
對Sun HotSpot而言,它是使用直接指針訪問方式進行對象訪問的尉间。
6.虛擬機優(yōu)化技術(shù)(后期)——逃逸分析
定義:逃逸分析是目前JVM中比較前沿的優(yōu)化技術(shù)偿乖,它不是直接的優(yōu)化手段而是為其他優(yōu)化手段提供依據(jù)的分析技術(shù)。逃逸分析的基本行為就是分析對象動態(tài)作用域哲嘲。
棧上分配:
虛擬機提供的一種優(yōu)化技術(shù)贪薪,基本思想是,對于線程私有的對象眠副,將它打散分配在棧上画切,而不分配在堆上。好處是對象跟著方法調(diào)用自行銷毀囱怕,不需要進行垃圾回收霍弹,可以提高性能。
棧上分配需要的技術(shù)基礎(chǔ)娃弓,逃逸分析典格。逃逸分析的目的是判斷對象的作用域是否會逃逸出方法體。
注意台丛,任何可以在多個線程之間共享的對象耍缴,一定都屬于逃逸對象。
虛擬機有一個逃逸技術(shù)齐佳,默認是開啟的私恬。
- 牽涉到的JVM參數(shù):
-XX:+DoEscapeAnalysis:啟用逃逸分析(默認打開) -XX:-DoEscapeAnalysis:關(guān)閉逃逸分析
-XX:+UseTLAB 本地線程分配緩沖(默認打開) 開啟了逃逸分析技術(shù),這個也要開炼吴。否則對象是沒有地方分配的本鸣。
-XX:+EliminateAllocations:標量替換(默認打開),即一個標準
這三個都要打開硅蹦,逃逸技術(shù)才起作用
其他一個可記: -XX:+PrintGC 打印垃圾回收的過程
使用堆和棧的好處是不同的:
使用棧荣德,它就可以跟著線程來跑。
使用堆呢童芹,你就必須要涉及到垃圾回收涮瞻。使用棧的話,線程關(guān)閉了假褪,它就沒有了署咽。
7.面試會問那哪些呢?
這些概念 注意內(nèi)存
程序計數(shù)器的問題
會問到棧,棧的特點 宁否。JVM中為什么用棧窒升,可以從方法和方法之間的調(diào)用去解釋。
虛擬棧的大小設(shè)置慕匠,比如虛擬機棧很有可能出現(xiàn)異常
寫遞歸一定要注意及時跳出饱须。
是不是所有對象都在堆上面分配? ----不是的台谊,因為有例外蓉媳,在棧上面分配