寫在前面:基於我的的知識對jvm進行白話理解,有不對的地方歡迎留言討論。java
1、JVM知識點的3條主線算法
3條主線都是圍繞JVM的內存模型展開,咱們把JVM的內存模型和數據庫進行橫向對比來理解基本概念。sql
2、JVM內存模型圖數據庫
基本內存模型安全
核心內存模型性能優化
(一)JVM內存模型重點數據結構
(二)白話理解概念多線程
咱們嘗試經過與數據庫橫向類比了解JVM的內存模型中4個最主要的區域,架構
爲何說常量池是另類空間呢,由於JAVA是面向對象的,理論上處理的全部事情都是類對象。而後Java語言在真實設計上須要處理的數據有兩類,1.基本類型,2.java定義的類,基於這個設計要求,因此jvm也作了相應的優化處理併發
1.若是是基本類型的數據,保存在常量池中。基本類型對應的如String、int、long、double、float、short、byte、boolean、char
2.若是是類對應的實例對象引用,保存在堆中.
這個常量池還有一個另類的地方,就是jdk1.6是定義在方法區的,jdk1.七、1.8之後是定義在堆空間的,爲何會這樣的,這是由於在jvm加載class文件處理常量的時候就用到了常量池,因此當時認爲他們是一塊兒的,其實咱們真實分析方法區的核心功能就是用來映射class文件到內存的數據模型,就是用來存儲元數據的;而堆就是用來存全局變量的,不管他是基本類型變量仍是java的對象實例,這樣的定位更清晰。
3、JVM的類加載機制
類加載過程圖
虛擬機類加載雙親委派順序圖
(一)JVM類加載機制重點
BootstrapClassLoader啓動類加載器:加載系統環境變量下JAVA_HOME/lib目錄下的類庫。
ExtClassLoader擴展類加載器:加載JAVA_HOME/lib/ext目錄下的類庫。
AppClassLoader應用程序類加載器(系統類加載器):加載用戶類路徑Class_Path指定的類庫。
自定義類加載器:若是須要自定義加載時的規則(好比:指定類的字節流來源、動態加載時性能優化等),能夠本身實現類加載器。
(二)白話理解概念
咱們依然嘗試經過與數據庫橫向類比了解JVM的類加載機制JVM的類加載相似於咱們執行建立表的sql腳本,最終的結果是在數據庫中完成建立元數據表、表的元數據記錄、業務表、業務表記錄。類加載機制是與內存模型關係最緊密:就是把class文件(實際工做中主要存在jar包中)加載進內存、而且把對象實例化。
加載:把class文件讀取到內存中;
準備、驗證、解析:就是把class文件的數據結構在內存中映射一份,這個時候涉及到若是是靜態常量就要給他初始化,若是是基本類型就要給他一個默認值,後面在理論部分詳細講解。
初始化:這個時候須要首先把類的靜態變量初始化,而後把成員變量初始化,而後構造,把對象放到堆中。初始化一個類的內部成員順序是核心,後面在理論部分詳細講解。
雙親委派就是兒子永遠相信父親,父親永遠相信爺爺。若是父親和爺爺都沒有提供方法,那就相信本身周邊。
JVM的雙親委派模型:就是jvm永遠認爲jdk定義的類是最可信的。爲何這麼說呢,首先咱們要知道JVM的classloader有3個等級,BootstrapClassLoader、ExtClassLoader、AppClassLoader。
AppClassLoader是咱們程序啓動的基本類加載器,他負責class文件的加載。若是咱們程序須要加載一個類,首先會請求AppClassLoader幹活,AppClassLoader他會先問他的領導ExtClassLoader裏面有沒有這個類,ExtClassLoader會繼續問他的領導BootstrapClassLoader裏面是否有這個類,若是BootstrapClassLoader裏面有就直接用了,若是沒有就告訴ExtClassLoader你本身去找吧,若是ExtClassLoader裏面也沒有那就告訴AppClassLoader你本身去搞定吧。
4、JVM的垃圾回收機制
JVMHeap區域(年輕代、老年代)和方法區(永久代)結構圖
(一)JVM垃圾回收機制重點
Mark-Sweep(標記-清除)算法
標記-清除算法分爲兩個階段:標記階段和清除階段,標記階段的任務是標記出全部須要被回收的對象,清除階段就是回收被標記的對象所佔用的空間。有碎片問題。
Copying(複製)算法
它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用的內存空間一次清理掉,這樣一來就不容易出現內存碎片的問題。有多花費內存空間問題。
Mark-Compact(標記-整理)算法
算法標記階段和Mark-Sweep同樣,可是在完成標記以後,它不是直接清理可回收對象,而是將存活對象都向一端移動,而後清理掉端邊界之外的內存
Generational Collection(分代收集)算法
分代收集算法是目前大部分JVM的垃圾收集器採用的算法。它的核心思想是根據對象存活的生命週期將內存劃分爲若干個不一樣的區域。通常狀況下將堆區劃分爲老年代(Tenured Generation)和新生代(Young Generation),老年代的特色是每次垃圾收集時只有少許對象須要被回收,而新生代的特色是每次垃圾回收時都有大量的對象須要被回收,那麼就能夠根據不一樣代的特色採起最適合的收集算法。
目前大部分垃圾收集器對於新生代都採起Copying算法,由於新生代中每次垃圾回收都要回收大部分對象,也就是說須要複製的操做次數較少,可是實際中並非按照1:1的比例來劃分新生代的空間的,通常來講是將新生代劃分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當進行回收時,將Eden和Survivor中還存活的對象複製到另外一塊Survivor空間中,而後清理掉Eden和剛纔使用過的Survivor空間。
而因爲老年代的特色是每次回收都只回收少許對象,通常使用的是Mark-Compact算法。
Serial/Serial Old:
Serial/Serial Old收集器是最基本最古老的收集器,它是一個單線程收集器,而且在它進行垃圾收集時,必須暫停全部用戶線程。Serial收集器是針對新生代的收集器,採用的是Copying算法,Serial Old收集器是針對老年代的收集器,採用的是Mark-Compact算法。它的優勢是實現簡單高效,可是缺點是會給用戶帶來停頓。
ParNew:
ParNew收集器是Serial收集器的多線程版本,使用多個線程進行垃圾收集。
Parallel Scavenge收集器是一個新生代的多線程收集器(並行收集器),它在回收期間不須要暫停其餘用戶線程,其採用的是Copying算法,該收集器與前兩個收集器有所不一樣,它主要是爲了達到一個可控的吞吐量。
Parallel Old:
Parallel Old是Parallel Scavenge收集器的老年代版本(並行收集器),使用多線程和Mark-Compact算法。
CMS:
CMS(Current Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器,它是一種併發收集器,採用的是Mark-Sweep算法
G1:
G1收集器是當今收集器技術發展最前沿的成果,它是一款面向服務端應用的收集器,它能充分利用多CPU、多核環境。所以它是一款並行與併發收集器,而且它能創建可預測的停頓時間模型。
(二)白話理解概念
咱們嘗試用數據庫的管理方式與JVM垃圾回收機型進行類比理解。
上面第三節講過JVM類加載器按照咱們的要求把類加載進來,就如同執行SQL腳本進行元數據表和業務表的建立,接下來程序就開始工做,程序的這個工做過程就相似與拼命的建立業務表,建立臨時表,刪除臨時表,向業務表中寫數據記錄。
這裏就有一個問題,若是業務表太多了,數據庫就要爆掉了,怎麼辦,這就有了JVM的垃圾回收機制,業務表太多了就要幹掉,這裏咱們知道了原來垃圾回收機制是用來管理業務表的,也就是JVM的堆空間的。
JVM的垃圾回收主要是管理堆空間,由於用戶程序在不停的建立對象,銷燬對象,防止堆內存空間不足,就要垃圾回收。
JVM都回收哪些空間呢,目前主流的就是分代垃圾回收機制,把表空間的業務表分紅兩類,剛建立的表就年輕表明,長久使用的叫老年表明,年輕代的表若是用一次就不用了直接幹掉,這就youngGC,老年代的表若是長時間不適用了也要幹掉,這就叫oldGC。若是年輕代裏面的表常用,就要從新歸類把他分到年老表明空間中。由於JVM在垃圾回收的時候是要中止程序運行的,因此咱們要儘可能減小老年代的GC。
5、JVM架構圖
6、JVM知識圖譜
三種狀況:
java7以前,方法區位於永久代(PermGen),永久代和堆相互隔離,永久代的大小在啓動JVM時能夠設置一個固定值,不可變;
java7中,存儲在永久代的部分數據就已經轉移到Java Heap或者Native memory。但永久代仍存在於JDK 1.7中,並無徹底移除,譬如符號引用(Symbols)轉移到了native memory;字符串常量池(interned strings)轉移到了Java heap;類的靜態變量(class statics)轉移到了Java heap。
java8中,取消永久代,方法存放於元空間(Metaspace),元空間仍然與堆不相連,但與堆共享物理內存,邏輯上可認爲在堆中
Native memory:本地內存,也稱爲C-Heap,是供JVM自身進程使用的。當Java Heap空間不足時會觸發GC,但Native memory空間不夠卻不會觸發GC。
知乎上:R大神解答:
Oracle JDK7 / OpenJDK 7的HotSpot VM是把Symbol的存儲從PermGen移動到了native memory,而且把靜態變量從instanceKlass末尾(位於PermGen內)移動到了java.lang.Class對象的末尾(位於普通Java heap內)。
「常量池」若是說的是SymbolTable / StringTable,這倆table自身本來就一直在native memory裏,是它們所引用的東西在哪裏更有意思。上面說了,7是把SymbolTable引用的Symbol移動到了native memory,而StringTable引用的java.lang.String實例則從PermGen移動到了普通Java heap。
傳送門:jdk8以後永久代去哪了?
因和這篇文章說的相同(R說的SymbolTable 即爲符號引用,StringTable即字符串常量),故認爲此理解正確,即 java7中,存儲在永久代的部分數據就已經轉移到Java Heap或者Native memory。但永久代仍存在於JDK 1.7中,並無徹底移除。譬如符號引用(Symbols)轉移到了native memory;字符串常量池(interned strings)轉移到了Java heap;類的靜態變量(class statics)轉移到了Java heap。
爲何移除永久代?
一、字符串存在永久代中,容易出現性能問題和內存溢出。
二、永久代大小不容易肯定,PermSize指定過小容易形成永久代OOM
三、永久代會爲 GC 帶來沒必要要的複雜度,而且回收效率偏低。
四、Oracle 可能會將HotSpot 與 JRockit 合二爲一。
在JDK1.7中, 已經把本來放在永久代的字符串常量池移出, 放在堆中. 爲何這樣作呢?
由於使用永久代來實現方法區不是個好主意, 很容易遇到內存溢出的問題. 咱們一般使用PermSize和MaxPermSize設置永久代的大小, 這個大小就決定了永久代的上限, 可是咱們不是老是知道應該設置爲多大的, 若是使用默認值容易遇到OOM錯誤。
類的元數據, 字符串池, 類的靜態變量將會從永久代移除, 放入Java heap或者native memory。其中建議JVM的實現中將類的元數據放入 native memory, 將字符串池和類的靜態變量放入java堆中. 這樣能夠加載多少類的元數據就不在由MaxPermSize控制, 而由系統的實際可用空間來控制.
爲何這麼作呢? 減小OOM只是表因, 更深層的緣由仍是要合併HotSpot和JRockit的代碼, JRockit歷來沒有一個叫永久代的東西, 可是運行良好, 也不須要開發運維人員設置這麼一個永久代的大小。固然不用擔憂運行性能問題了, 在覆蓋到的測試中, 程序啓動和運行速度下降不超過1%, 可是這一點性能損失換來了更大的安全保障。