深入理解jvm虚拟机一
深入理解jvm虛擬機(jī)一
- JVM
- Jvm基本概念
- 運(yùn)行過程
- 內(nèi)存區(qū)域
- 程序計(jì)數(shù)器
- 虛擬機(jī)棧
- 本地方法棧
- 堆
- 方法區(qū)/永久代
- 1.8元數(shù)據(jù)區(qū)
- 回收與收集
- 兩大回收
- 引用計(jì)數(shù)法
- 可達(dá)性分析
- 四大引用
- 強(qiáng)引用
- 軟引用
- 弱引用
- 虛引用
- 回收算法
- 標(biāo)記-清除(Mark-Sweep)
- 復(fù)制(Copying)
- 標(biāo)記-整理(Mark-Compact)
- 分代算法
- 新生代
- 老年代
JVM
Java與Jvm的關(guān)系似魚和水,而開發(fā)者與Jvm的關(guān)系似情侶相愛相殺。愛它不用像C、C++擺弄指針,把內(nèi)存控制的權(quán)利交給它,恨它一旦出現(xiàn)內(nèi)存泄漏和溢出方面的問題,如果不理解它的話,無從下手,更別談優(yōu)化了。
Jvm基本概念
JVM及Java虛擬機(jī),是可運(yùn)行Java代碼的假象計(jì)算機(jī),Jvm是運(yùn)行在操作系統(tǒng)之上的,它與硬件沒有直接交互。
運(yùn)行過程
我們都知道Java源文件,通過編譯器,能夠生成相應(yīng)的.class文件,也就是字節(jié)碼文件,而字節(jié)碼文件又能通過jvm的解釋器,編譯成機(jī)器上的機(jī)器碼,大概流程如下:Java源文件—>編譯器—>字節(jié)碼文件—>jvm—>機(jī)器碼,雖然每個(gè)平臺(tái)的解釋器不同,但是虛擬機(jī)是相同的,這也就是為什么java是跨平臺(tái)的。
內(nèi)存區(qū)域
Jvm把Java程序運(yùn)行時(shí)的內(nèi)存劃分為不同的數(shù)據(jù)區(qū)域,如圖所示:
jvm內(nèi)存區(qū)域主要分為線程私有(程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧)、線程共享(堆、方法區(qū))、直接內(nèi)存,線程私有的生命周期與線程相同,依賴用戶線程的啟動(dòng)/結(jié)束而創(chuàng)建/銷毀,線程共享隨虛擬機(jī)開啟/關(guān)閉而創(chuàng)建/銷毀。直接內(nèi)存并不是jvm運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,在 JDK 1.4 引入的 NIO 提供了基于 Channel 與 Buffer 的 IO 方式, 它可以使用 Native 函數(shù)庫直接分配堆外內(nèi)存,然后使用DirectByteBuffer 對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作,這樣就避免了在 Java堆和 Native 堆中來回復(fù)制數(shù)據(jù),因此在一些場景中可以顯著提高性能。
程序計(jì)數(shù)器
一塊較小的內(nèi)存空間,是當(dāng)前線程所執(zhí)行到的字節(jié)碼的行號(hào)指令器,每個(gè)線程都是獨(dú)立運(yùn)行的,如果正在執(zhí)行的是Java方法,則記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼行號(hào),如果是native方法,則為空,此區(qū)域也是唯一一個(gè)在虛擬機(jī)中沒有規(guī)定任何 OOM 情況的區(qū)域。
虛擬機(jī)棧
生命周期與線程相同,是描述Java方法執(zhí)行的內(nèi)存模型,每個(gè)方法在執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧針,用于存放局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)方法從調(diào)用直至執(zhí)行結(jié)束對(duì)應(yīng)棧針在虛擬機(jī)中的入棧和出棧。如果虛擬機(jī)棧請(qǐng)求棧深度大于虛擬機(jī)所允許的深度,將拋出StrakOverflowError異常;如果擴(kuò)展時(shí)無法申請(qǐng)到足夠的內(nèi)存,將拋出OOM異常,可以通過xss參數(shù)來調(diào)節(jié)大小,默認(rèn)1M。
本地方法棧
與虛擬機(jī)中作用相似,區(qū)別是虛擬機(jī)棧為Java方法服務(wù)的,而本地方法棧是為native服務(wù)的,它也會(huì)拋出同虛擬機(jī)棧一樣的異常。
堆
是用來存放被創(chuàng)建的對(duì)象、數(shù)組,也是垃圾回收器進(jìn)行垃圾回收的主要內(nèi)存區(qū)域,可以通過xms、xmx來設(shè)置初始化堆大小、最大堆大小,默認(rèn)是最小1/64,最大1/4。由于現(xiàn)代的垃圾回收器都是采用分代回收,因此堆從GC的角度還可以細(xì)分為:新生代(Eden區(qū)、From Survivor區(qū)、To Survivor區(qū))和老年代。
方法區(qū)/永久代
用來存放被Jvm加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼數(shù)據(jù),HotSpot VM把GC分代收集擴(kuò)展至方法區(qū), 即使用Java堆的永久代來實(shí)現(xiàn)方法區(qū), 這樣 HotSpot 的垃圾收集器就可以像管理 Java 堆一樣管理這部分內(nèi)存,而不必為方法區(qū)開發(fā)專門的內(nèi)存管理器(永久帶的內(nèi)存回收的主要目標(biāo)是針對(duì)常量池的回收和類型的卸載,因此收益一般很小),通過設(shè)置PermSize和MaxPermSize設(shè)置初始化和最大上限(1.7以前)。
1.8元數(shù)據(jù)區(qū)
在Jdk1.8取消了永久代,不在使用虛擬機(jī)內(nèi)的永久代而是改用本地內(nèi)存——元數(shù)據(jù)區(qū)(Metaspace),通過MetaspaceSize和MaxMetaspaceSize來設(shè)置初始化和最大上限。至于為什么不使用,理由如下:
1. 永久代調(diào)優(yōu)很難,會(huì)為 GC 帶來不必要的復(fù)雜度,并且回收效率偏低。
2. 字符串存在永久代中,容易出現(xiàn)性能問題和內(nèi)存溢出。
3. 類及方法的信息等比較難確定其大小,因此對(duì)于永久代的大小指定比較困難,太小容易出現(xiàn)永久代溢出,太大則容易導(dǎo)致老年代溢出。
4. Oracle 可能會(huì)將HotSpot 與 JRockit 合二為一。
回收與收集
兩大回收
在堆里幾乎存放了所有的實(shí)例,垃圾回收前,需要判斷哪些對(duì)象“活著”,哪些對(duì)象“死去”。
引用計(jì)數(shù)法
在java中引用和對(duì)象是有關(guān)聯(lián)的,如果操作對(duì)象就必須用引用來進(jìn)行。因此通過引用計(jì)數(shù)法來判斷一個(gè)對(duì)象是否可以被回收,被引用則計(jì)數(shù)器+1,引用失效則計(jì)數(shù)器-1,直到引用計(jì)數(shù)為0,這是基本的也是比較高效的,但是無法避免相互引用。
可達(dá)性分析
為了避免相互引用問題,Java使用的可達(dá)性分析的方法,通過“GC roots”對(duì)象作為起點(diǎn)搜索,如果沒有可達(dá)路徑,則該對(duì)象不可達(dá),可以被回收。即使這樣,也不是說必須回收,如果該對(duì)象覆蓋了finalize()方法,重新引用對(duì)象可以進(jìn)行自救,只會(huì)自救一次。因?yàn)檫@個(gè)方法對(duì)運(yùn)行代價(jià)高,不穩(wěn)定性,因此也不推薦使用。在Java中GC roots對(duì)象包括下面四種:
1. 虛擬機(jī)棧(棧針中變量表)引用的對(duì)象
2. 本地方法棧(JNI)引用的對(duì)象
3. 方法區(qū)中類靜態(tài)屬性引用的對(duì)象
4. 方法區(qū)中常量引用的對(duì)象
四大引用
無論是引用計(jì)數(shù)法還是可達(dá)性分析,都與Java中引用有關(guān)。
強(qiáng)引用
Java中常見的引用,Object obj=new Object,即使虛擬機(jī)拋出OOM(內(nèi)存溢出)也不會(huì)釋放這部分引用,這部分容易造成內(nèi)存泄漏。
軟引用
需要用SoftReference類實(shí)現(xiàn),描述一些還有用但非必需的對(duì)象,當(dāng)系統(tǒng)內(nèi)存足夠時(shí),是不會(huì)回收這部分資源,只有在系統(tǒng)內(nèi)存不夠時(shí),這部分則會(huì)被回收。
弱引用
需要用WeakReference類實(shí)現(xiàn),它比軟引用存活時(shí)間更短,它的生命周期只存活在下一次垃圾回收之前。
虛引用
需要 PhantomReference 類來實(shí)現(xiàn),隨時(shí)被回收,它不能單獨(dú)使用,必須和引用隊(duì)列聯(lián)合使用,虛引用的主要作用是跟蹤對(duì)象被垃圾回收的狀態(tài)。
回收算法
標(biāo)記-清除(Mark-Sweep)
最基本的垃圾回收算法,分為兩個(gè)階段:標(biāo)記、清除。標(biāo)記可以被回收的對(duì)象,清除所有被標(biāo)記的對(duì)象。
缺點(diǎn):空間碎片化嚴(yán)重,標(biāo)記清除,效率都不高。
復(fù)制(Copying)
為了解決碎片化問題,按內(nèi)存容量一分為二,每次只用其中的一塊,當(dāng)一塊存滿時(shí)候,把存活的對(duì)象復(fù)制到另外一塊上,把已使用的內(nèi)存塊清除。
缺點(diǎn):內(nèi)存使用率低。
標(biāo)記-整理(Mark-Compact)
結(jié)合以上兩個(gè)算法,標(biāo)記類似于Mark-Sweep,標(biāo)記后不是清理對(duì)象,而是把存活的對(duì)象移向內(nèi)存的一端,把端以外的對(duì)象清除。
分代算法
分代算法是目前大多數(shù)JVM使用的回收算法,根據(jù)對(duì)象存活的不同生命周期來劃分內(nèi)存區(qū)域,一般化為老年代和新生代,老年代特點(diǎn)每次垃圾回收只有少量對(duì)象被回收,新生代特點(diǎn)每次垃圾回收都會(huì)有大量對(duì)象被回收,這樣就可以根據(jù)不同區(qū)域來選擇不同的回收算法。
新生代
目前大多數(shù)JVM新生代都是采用復(fù)制算法,因?yàn)樾律枰厥沾蟛糠謱?duì)象,并不是按照1:1來劃分新生代,而是劃分為一塊較大的Eden空間和兩個(gè)較小的Survivor空間(from ,to),每次都使用Eden和一部分Survivor區(qū)域,當(dāng)進(jìn)行回收時(shí),把存活的對(duì)象復(fù)制到另一塊Survivor空間,通過xmn調(diào)整新生代大小。
對(duì)象優(yōu)先分配在新生代,大對(duì)象直接則進(jìn)入老年代(設(shè)置-xx:PretenureSizeThreshold參數(shù)),長期存活的對(duì)象進(jìn)入老年代(設(shè)置-xx:MaxTenuringThreshold,默認(rèn)15次),為了更加適應(yīng)程序的內(nèi)存狀態(tài),并不是要求年齡必須到15才會(huì)進(jìn)入老年代,如果在Survivor空間中所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象也可以直接進(jìn)入老年代。
老年代
老年代存放生命周期比較長的,對(duì)象相對(duì)穩(wěn)定,所以采用標(biāo)記清除或者整理。
Minor GC:當(dāng)新生代內(nèi)存不足,回收新生代(Ende區(qū)和兩個(gè)Survivor)內(nèi)存。
Major GC:清理老年代,一般伴隨著一次Minor,效率比Minor要慢。
Full GC:清理整個(gè)堆空間,一般調(diào)優(yōu)也就是堆fullGC進(jìn)行優(yōu)化,因?yàn)樗鼤?huì)停止操作,單一線程進(jìn)行GC。
總結(jié)
以上是生活随笔為你收集整理的深入理解jvm虚拟机一的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springMVC 后端代码 如何用 @
- 下一篇: matlab画线不同颜色_怎样画线框图才