深刻理解jvm虛擬機一

JVM

Java與Jvm的關係似魚和水,而開發者與Jvm的關係似情侶相愛相殺。愛它不用像C、C++擺弄指針,把內存控制的權利交給它,恨它一旦出現內存泄漏和溢出方面的問題,若是不理解它的話,無從下手,更別談優化了。java

Jvm基本概念

JVM及Java虛擬機,是可運行Java代碼的假象計算機,Jvm是運行在操做系統之上的,它與硬件沒有直接交互。算法

運行過程

咱們都知道Java源文件,經過編譯器,可以生成相應的.class文件,也就是字節碼文件,而字節碼文件又能經過jvm的解釋器,編譯成機器上的機器碼,大概流程以下:Java源文件—>編譯器—>字節碼文件—>jvm—>機器碼,雖然每一個平臺的解釋器不一樣,可是虛擬機是相同的,這也就是爲何java是跨平臺的。數組

內存區域

Jvm把Java程序運行時的內存劃分爲不一樣的數據區域,如圖所示: jvm

jvm內存模型
jvm內存區域主要分爲 線程私有(程序計數器、虛擬機棧、本地方法棧)、線程共享(堆、方法區)、直接內存,線程私有的生命週期與線程相同,依賴用戶線程的啓動/結束而建立/銷燬,線程共享隨虛擬機開啓/關閉而建立/銷燬。直接內存並非jvm運行時數據區的一部分,在 JDK 1.4 引入的 NIO 提供了基於 Channel 與 Buffer 的 IO 方式, 它可使用 Native 函數庫直接分配堆外內存,而後使用DirectByteBuffer 對象做爲這塊內存的引用進行操做,這樣就避免了在 Java堆和 Native 堆中來回複製數據,所以在一些場景中能夠顯著提升性能。

程序計數器

一塊較小的內存空間,是當前線程所執行到的字節碼的行號指令器,每一個線程都是獨立運行的,若是正在執行的是Java方法,則記錄的是正在執行的虛擬機字節碼行號,若是是native方法,則爲空,此區域也是惟一一個在虛擬機中沒有規定任何 OOM 狀況的區域。xss

虛擬機棧

生命週期與線程相同,是描述Java方法執行的內存模型,每一個方法在執行的時候都會建立一個棧針,用於存放局部變量表、操做數棧、動態連接、方法出口等信息。每個方法從調用直至執行結束對應棧針在虛擬機中的入棧和出棧。若是虛擬機棧請求棧深度大於虛擬機所容許的深度,將拋出StrakOverflowError異常;若是擴展時沒法申請到足夠的內存,將拋出OOM異常,能夠經過xss參數來調節大小,默認1M。函數

本地方法棧

與虛擬機中做用類似,區別是虛擬機棧爲Java方法服務的,而本地方法棧是爲native服務的,它也會拋出同虛擬機棧同樣的異常。性能

是用來存放被建立的對象、數組,也是垃圾回收器進行垃圾回收的主要內存區域,能夠經過xms、xmx來設置初始化堆大小、最大堆大小,默認是最小1/64,最大1/4。因爲現代的垃圾回收器都是採用分代回收,所以堆從GC的角度還能夠細分爲:新生代(Eden區、From Survivor區、To Survivor區)和老年代。優化

方法區/永久代

用來存放被Jvm加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼數據,HotSpot VM把GC分代收集擴展至方法區, 即便用Java堆的永久代來實現方法區, 這樣 HotSpot 的垃圾收集器就能夠像管理 Java 堆同樣管理這部份內存,而沒必要爲方法區開發專門的內存管理器(永久帶的內存回收的主要目標是針對常量池的回收和類型的卸載,所以收益通常很小),經過設置PermSize和MaxPermSize設置初始化和最大上限(1.7之前)spa

1.8元數據區

在Jdk1.8取消了永久代,不在使用虛擬機內的永久代而是改用本地內存——元數據區(Metaspace),經過MetaspaceSize和MaxMetaspaceSize來設置初始化和最大上限。至於爲何不使用,理由以下: 1. 永久代調優很難,會爲 GC 帶來沒必要要的複雜度,而且回收效率偏低。 2. 字符串存在永久代中,容易出現性能問題和內存溢出。 3. 類及方法的信息等比較難肯定其大小,所以對於永久代的大小指定比較困難,過小容易出現永久代溢出,太大則容易致使老年代溢出。 4. Oracle 可能會將HotSpot 與 JRockit 合二爲一。操作系統

回收與收集

兩大回收

在堆裏幾乎存放了全部的實例,垃圾回收前,須要判斷哪些對象「活着」,哪些對象「死去」。

引用計數法

在java中引用和對象是有關聯的,若是操做對象就必須用引用來進行。所以經過引用計數法來判斷一個對象是否能夠被回收,被引用則計數器+1,引用失效則計數器-1,直到引用計數爲0,這是基本的也是比較高效的,可是沒法避免相互引用。

可達性分析

爲了不相互引用問題,Java使用的可達性分析的方法,經過「GC roots」對象做爲起點搜索,若是沒有可達路徑,則該對象不可達,能夠被回收。即便這樣,也不是說必須回收,若是該對象覆蓋了finalize()方法,從新引用對象能夠進行自救,只會自救一次。由於這個方法對運行代價高,不穩定性,所以也不推薦使用。在Java中GC roots對象包括下面四種: 1. 虛擬機棧(棧針中變量表)引用的對象 2. 本地方法棧(JNI)引用的對象 3. 方法區中類靜態屬性引用的對象 4. 方法區中常量引用的對象

四大引用

不管是引用計數法仍是可達性分析,都與Java中引用有關。

強引用

Java中常見的引用,Object obj=new Object,即便虛擬機拋出OOM(內存溢出)也不會釋放這部分引用,這部分容易形成內存泄漏。

軟引用

須要用SoftReference類實現,描述一些還有用但非必需的對象,當系統內存足夠時,是不會回收這部分資源,只有在系統內存不夠時,這部分則會被回收。

弱引用

須要用WeakReference類實現,它比軟引用存活時間更短,它的生命週期只存活在下一次垃圾回收以前。

虛引用

須要 PhantomReference 類來實現,隨時被回收,它不能單獨使用,必須和引用隊列聯合使用,虛引用的主要做用是跟蹤對象被垃圾回收的狀態。

回收算法

標記-清除(Mark-Sweep)

最基本的垃圾回收算法,分爲兩個階段:標記、清除。標記能夠被回收的對象,清除全部被標記的對象。 缺點:空間碎片化嚴重,標記清除,效率都不高。

標記清除

複製(Copying)

爲了解決碎片化問題,按內存容量一分爲二,每次只用其中的一塊,當一塊存滿時候,把存活的對象複製到另一塊上,把已使用的內存塊清除。 缺點:內存使用率低。

複製算法

標記-整理(Mark-Compact)

結合以上兩個算法,標記相似於Mark-Sweep,標記後不是清理對象,而是把存活的對象移向內存的一端,把端之外的對象清除。

標記整理

分代算法

分代算法是目前大多數JVM使用的回收算法,根據對象存活的不一樣生命週期來劃份內存區域,通常化爲老年代和新生代,老年代特色每次垃圾回收只有少許對象被回收,新生代特色每次垃圾回收都會有大量對象被回收,這樣就能夠根據不一樣區域來選擇不一樣的回收算法。

新生代

目前大多數JVM新生代都是採用複製算法,由於新生代須要回收大部分對象,並非按照1:1來劃分新生代,而是劃分爲一塊較大的Eden空間和兩個較小的Survivor空間(from ,to),每次都使用Eden和一部分Survivor區域,當進行回收時,把存活的對象複製到另外一塊Survivor空間,經過xmn調整新生代大小。

新生代
對象優先分配在新生代,大對象直接則進入老年代(設置-xx:PretenureSizeThreshold參數),長期存活的對象進入老年代(設置-xx:MaxTenuringThreshold,默認15次),爲了更加適應程序的內存狀態,並非要求年齡必須到15纔會進入老年代,若是在Survivor空間中全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象也能夠直接進入老年代。

老年代

老年代存放生命週期比較長的,對象相對穩定,因此採用標記清除或者整理。 Minor GC:當新生代內存不足,回收新生代(Ende區和兩個Survivor)內存。 Major GC:清理老年代,通常伴隨着一次Minor,效率比Minor要慢。 Full GC:清理整個堆空間,通常調優也就是堆fullGC進行優化,由於它會中止操做,單一線程進行GC。

相關文章
相關標籤/搜索