1、 JVM內存分區java
分爲程序計數器、虛擬機棧、本地方法棧、Java堆、方法區5個區域程序員
其中Java堆和方法區是線程共享的,虛擬機棧、本地方法棧、程序計數器是線程隔離的。算法
程序計數器:數組
1.能夠看做當前線程所執行的字節碼的行號指示器緩存
2.Java多線程之間進行切換的時候須要以後恢復到以前執行位置,因此每條線程須要一個程序計數器,程序計數器是線程隔離的。安全
3.不會發生內存溢出數據結構
虛擬機棧(堆內存):多線程
描述的是Java方法執行的內存模型,每一個方法在執行的同時會建立一個棧幀用於存儲局部變量表什麼的。方法的調用和執行完成對應着 棧幀在虛擬機棧中的入棧和出棧。併發
局部變量表裏放的是編譯期可知的基本類型和對象引用和returnAdress。局部變量表所需的內存空間在編譯期間完成分配,運行期間不會改變。jvm
線程請求的棧深度大於虛擬機容許會發生Stackoverflowerror,擴展棧時若是沒法申請到足夠空間會發生OutOfMemoryError.
本地方法棧:
與虛擬機棧相似,可是本地方法棧是爲native方法服務的。
一個Native Method就是一個java調用非java代碼的接口。一個Native Method是這樣一個java的方法:該方法的實現由非java語言實現,好比C。
和虛擬機棧同樣,Stackoverflowerror和OutOfMemoryError
Java堆:
被全部線程共享的區域,用來存放對象實例,幾乎全部對象實例都在這分配內存
Java堆沒有內存分配了而且沒法擴展時拋出OutOfMemoryError
方法區:
全部線程共享的區域,用於存儲已被加載的類信息、常量、靜態變量等數據。
Java8以前HotSpot使用永久代實現方法區,如今Java8移除了永久代。改用了元空間,使用的是本地內存,非jvm區域。
沒法知足內存分配時拋出OutOfMemoryError。
知道的GC算法有四種:標記-清除算法、複製算法、標記整理、分代收集算法。
標記-清除算法:
分「標記」和「清除」兩個階段:先標記出全部須要回收的對象,標記完成後統一回收。
最基礎的收集算法,有兩個缺陷:1.效率不高 2.會產生大量不連續的內存碎片,致使以後分配大對象空間不夠,而不得又觸發垃圾回收。
複製算法:
將可用內存按照容量劃分爲大小相等的兩塊,每次只使用其中一塊。第一塊用完了,就將第一塊裏存活的複製到第二塊,再回收掉第一塊所有。
這樣不用考慮內存碎片且運行高效。可是代價是縮小一半可用,代價大。
而後實際上會把可用內存空間按8:1:1分爲Eden和兩塊Survivor 並稱爲新生代,每次使用Eden和一塊Survivor也就是90%,回收時把存活的對象放到剩餘的那塊Survivor上,再回收。減小浪費。若是剩餘那塊Survivor放不下,就會放到Java堆的老年代裏去。
標記-整理算法:
因爲可能存在大量對象存活的狀況,那麼複製算法複製較多效率會變低。
而根據老年代存活對象較多的特色,用標記-整理算法,與標記清除算法區別在於標記後得把活着的整理到一端,再把另外一端回收。
分代收集算法:
並無新的收集算法,而是把Java堆按2:1分爲新生代和老年代,根據特色選用收集算法。新生代採用複製算法,老年代採用標記-整理或標記-清除。
Serial收集器:
單線程收集器,只會使用一條線程去完成收集,在收集時必須暫停其它全部工做線程。
缺點很明顯,但優勢在於簡單而高效,沒有線程交互開銷
在clien的用戶程序模式下,暫停幾十毫秒,用戶感覺不到,能夠接收。
ParNew收集器:
Serial的多線程版本,多線程去垃圾回收.
Server模式下虛擬機首選的新生代收集器,緣由在於除Serial收集器外只要它能夠和CMS這款強大的併發老年代收集器配合。
ParallelScavenge收集器:
特色在於關注點和其它收集器不一樣,CMS等收集器關注點在於儘量縮短用戶線程的停頓時間,而Parallel Scavenge收集器目的是控制吞吐量,即控制用戶代碼時間在用戶代碼時間+GC時間中的比例。
SerialOld收集器:
Serial收集器的老年代版本,採用標記-整理算法。主要給客戶端模式下的虛擬機使用。在Server模式下兩個用途:jdk1.5以前和Parallel Scavenge收集器搭配使用,其次是做爲CMS收集器的後備預案
ParallelOld收集器:
ParallelScavenge收集器的老年版本。與新生代收集器Parallel Scavenge 配合實現吞吐量的控制和利用多處理器能力。
CMS收集器:
以最短回收停頓時間爲目標的收集器,「標記-清除」算法,過程分爲四個步驟:
1. 初始標記:標記GC Roots能直接關聯到的對象
2. 併發標記:順着GC Roots尋找的過程
3. 從新標記:修改併發標記期間程序運行而改動的對象
4. 併發清除
其中初始標記、從新標記須要中止其它工做線程,併發標記和併發清除不須要。
因爲整個過程當中耗時最長的併發標記和併發清除過程是能夠和用戶線程一塊兒的,因此整體上CMS收集器是能夠和用戶線程一塊兒的。
優勢:併發收集、低停頓
缺點:
1. 佔用大量CPU資源
2. CMS收集器沒法處理浮動垃圾,可能會出現「Concurrent Mode Failure(併發模式故障)」失敗而致使Full GC產生。
浮動垃圾:因爲CMS併發清理階段用戶線程還在運行着,伴隨着程序運行天然就會有新的垃圾不斷產生,這部分垃圾出如今標記過程以後,CMS沒法在當次收集中處理掉它們,只好留待下一次GC中再清理。這些垃圾就是「浮動垃圾」。
3. 標記-清除容易產生空間碎片,容易提早觸發Full GC。
G1收集器:
但願將來能夠替代CMS的收集器。將Java堆劃分爲多個大小相等的獨立區域。
1. 並行和併發
2. 分代收集,且G1就能夠獨立管理整個Java堆
3. 總體上採用「標記-整理」、局部(兩個獨立區域之間)上是複製算法。
4. 能夠指定某個時間片斷上,垃圾收集不超過的時間:維護了獨立區域的回收價值優先列表,優先回收回收價值高的(Garbage First名字由來),實現指定時間效率。
回收步驟:
1. 初始標記(標記GC Roots能直接關聯到的對象)
2. 併發標記(順着GC Roots尋找的過程,只有這個是併發的,表示和用戶一塊兒)
3. 最終標記(修改併發標記期間程序運行而改動的對象)
4. 篩選回收
Full GC定義是相對明確的,就是針對整個新生代、老生代、元空間(metaspace,java8以上版本取代perm gen)的全局範圍的GC;Minor GC和Major GC是俗稱,對應着Young GC和Old GC
Minor GC ,FullGC 觸發條件
Minor GC觸發條件:當Eden區滿時,觸發Minor GC。
Full GC觸發條件:
(1)調用System.gc時,系統建議執行Full GC,可是沒必要然執行
(2)老年代空間不足
1.包括複製算法移到這時,
2.堆上分配大對象或大數組直接進入老年代時,
3.經過Minor GC時,計算以前晉升到老年代的平均大小,當老年代剩餘空間小於平均值時
(3)若是有永久代的話,永久代不夠用也會觸發
(4)CMS GC時出現concurrent mode failure
強引用、軟引用、弱引用、虛引用。
強引用:是指建立一個對象並把這個對象賦給一個引用變量。
好比:Object object =new Object();,只有強引用還在,就不會回收
軟引用:若是一個對象具備軟引用,內存空間足夠,垃圾回收器就不會回收它;
若是內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就能夠被程序使用。可使用SoftReference類實現。
弱引用:弱引用也是用來描述非必需對象的,比軟引用更弱,當JVM進行垃圾回收時,不管內存是否充足,都會回收被弱引用關聯的對象。能夠用WeakReference類實現
虛引用:不影響對象的生命週期。java中用PhantomReference類表示。若是一個對象與虛引用關聯,則跟沒有引用與之關聯同樣,在任什麼時候候均可能被垃圾回收器回收。惟一目的是爲了回收時有個系統通知。
主流的商用程序語言中(Java和C#),都是使用可達性分析算法判斷對象是否存活的。基本思路就是經過一系列名爲"GC Roots"的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的,下圖對象object5, object6, object7雖然有互相關聯,但它們到GCRoots是不可達的,因此它們將會斷定爲是可回收對象。
虛擬機棧中引用的對象、
方法區類靜態屬性引用的對象、
方法區常量引用的對象、
本地方法棧JNI(Native方法)引用的對象
1.內存泄漏:是指程序在申請內存後,沒法釋放已申請的內存空間,一次內存泄漏彷佛不會有大的影響,但內存泄漏堆積後的後果就是內存溢出。
2、內存溢出指程序申請內存時,沒有足夠的內存供申請者使用,或者說,給了你一塊存儲int類型數據的存儲空間,可是你卻存儲long類型的數據,那麼結果就是內存不夠用,此時就會報錯OOM,即所謂的內存溢出。
3、兩者的關係
內存泄漏的堆積最終會致使內存溢出內存溢出就是你要的內存空間超過了系統實際分配給你的空間,此時系統至關於無法知足你的需求,就會報內存溢出的錯誤。內存泄漏是指你向系統申請分配內存進行使用(new),但是使用完了之後卻不歸還,結果你申請到的那塊內存你本身也不能再訪問(也許你把它的地址給弄丟了),而系統也不能再次將它分配給須要的程序。
商業虛擬機都採用分代收集算法,將Java堆按1:2的比例分紅新生代和老年代。根據不一樣年代採用不一樣收集算法,新生代存的都是新生的對象,垃圾收集時大多都會死,適合採用複製算法。而老年代存儲的都是生命週期較長的,回收的較少,適合採用標記-整理或標記-清除。
分代的目的:(縮短GC致使的停頓時間)
一般GC的整個工做過程當中都要「stop-the-world」,若是能想辦法縮短GC一次工做的時間長度就是件重要的事情。若是說收集整個GC堆耗時太長,那不如只收集其中的一部分
新生代和老年代存儲的是什麼:這就關於jvm內存分配的問題:
新生代:新建立的對象優先在新生代的Eden上分配。
老年代:
1.新生代對象每經歷依次minor gc,年齡會加一,當達到年齡閥值會直接進入老年代。閥值大小通常爲15
2.Survivor中年齡相同的對象數量的總和大於survivor空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代,而無需等到年齡閥值
3.大對象大數組直接進入老年代
4.新生代複製算法須要一個survivor區進行輪換備份,若是出現大量對象在minor gc後仍然存活的狀況時,就須要老年代進行分配擔保,讓survivor沒法容納的對象直接進入老年代
什麼是類加載機制:
虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。以下:
有加載、驗證、準備、解析、初始化、使用、卸載七個階段,其中驗證、準備、解析統稱爲鏈接。其中除解析外,其它都是按順序開始的(幾個步驟可能同時進行),解析則有可能在初始化以後纔開始,這是爲了支持Java語言的運行時綁定(也稱爲動態綁定或晚期綁定)。
虛擬機對於類的初始化階段嚴格規定了有且僅有隻有5種狀況若是對類沒有進行過初始化,則必須對類進行「初始化」!
1. 遇到new、讀取一個類的靜態字段(getstatic)、設置一個類的靜態字段(putstatic)、調用一個類的靜態方法(invokestatic)。
2. 使用java.lang.reflect包的方法對類進行反射調用時。
3. 當類初始化一個類的時候,若是發現其父類尚未進行過初始化,則須要先觸發其父類的初始化。(若是是接口,則沒必要觸發其父類初始化)
4. 當虛擬機執行一個main方法時,會首先初始化main所在的這個主類。
5. 當只用jdk1.7的動態語言支持時,若是一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而且這個方法句柄所對應的類沒有進行過初始化,則須要先觸發其初始化。(暫未研究此種場景)
驗證是鏈接階段的第一步,這一階段的目的是爲了確保 Class 文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。
準備階段是正式爲類變量分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區進行分配。而實例變量是在類實例化的時候初始化,和類加載不要緊!
解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程
類初始化階段是類加載過程的最後一步,前面的類加載過程當中,除了在加載階段用戶應用程序能夠經過自定義類加載器參與以外,其他動做徹底由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的 Java 程序代碼(或者說是字節碼)。
對於任意一個類,都須要由加載它的類加載器和這個類自己一同確立在Java虛擬機的惟一性。比較兩個類是否相等不能光看他們自己來自同一個類,同時也要看加載它們的類加載器是否相同。
絕大部分Java程序會使用到如下3種系統提供的類加載器。
1. 啓動類加載器(Bootstrap ClassLoader),這個類加載器使用C++實現,是虛擬機自身的一部分。負責加載<JAVA_HOME>\lib中(或者-Xbootclasspath參數所指定路徑),而且是虛擬機識別的類庫 加載到虛擬機內存中。不可直接被Java程序引用。
2. 擴展類加載器(Extension ClassLoader),這個類加載器由Java實現,獨立於虛擬機外部。負責加載<JAVA_HOME>\lib\ext中(或者被java.ext.dirs系統變量所指定)的全部類庫。開發者可直接使用擴展類加載器。
3. 應用程序類加載器(Application ClassLoader),也稱之爲系統類加載器,一樣也由Java實現,獨立於虛擬機外部。負責加載用戶類路徑(ClassPath)上所指定的類庫。開發者可直接使用這個類加載器。
4. 若是有必要能夠本身定義類加載器(繼承ClassLoader,重寫方法)
若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試這個類,而是把這個請求委派給父類加載器去完成,每個層次的類加載器都是如此,所以全部的加載請求最終都應該傳送到頂層的啓動類加載器中,只有當父加載器反饋本身沒法完成這個加載請求(它的搜索範圍中沒有找到所需的類)時,子加載器纔會嘗試本身去加載。
對Java程序穩定運做很重要,防止類混亂,好比你就不可能去編寫java.lang.Object了,層層上交到頂層會發現已存在。
能不能本身寫個類叫java.lang.System?
答案:一般不能夠,但能夠採起另類方法達到這個需求。
解釋:爲了避免讓咱們寫System類,類加載採用委託機制,這樣能夠保證爸爸們優先,爸爸們能找到的類,兒子就沒有機會加載。而System類是Bootstrap加載器加載的,就算本身重寫,也老是使用Java系統提供的System,本身寫的System類根本沒有機會獲得加載。
可是,咱們能夠本身定義一個類加載器來達到這個目的,爲了不雙親委託機制,這個類加載器也必須是特殊的。因爲系統自帶的三個類加載器都加載特定目錄下的類,若是咱們本身的類放在一個特殊的目錄,那麼系統的加載器就沒法加載,也就是最終仍是由咱們本身的加載器加載。
java是面向對象的語言,所以對象的建立無時無刻都存在。在語言層面,使用new關鍵字便可建立出一個對象。可是在虛擬機中,對象建立的建立過程則是比較複雜的。
首先,虛擬機運到new指令時,會去常量池檢查是否存在new指令中包含的參數,好比newPeople(),則虛擬機首先會去常量池中檢查是否有People這個類的符號引用,而且檢查這個類是否已經被加載了,若是沒有則會執行類加載過程。
在類加載檢查事後,接下來爲對象分配內存固然是在java堆中分配,而且對象所須要分配的多大內存在類加載過程當中就已經肯定了。爲對象分配內存的方式根據java堆是否規整分爲兩個方法:1、指針碰撞(Bump thePointer),2、空閒列表(Free List)。指針碰撞:若是java堆是規整的,即全部用過的內存放在一邊,沒有用過的內存放在另一邊,而且有一個指針指向分界點,在須要爲新生對象分配內存的時候,只須要移動指針畫出一塊內存分配和新生對象便可;空閒列表:當java堆不是規整的,意思就是使用的內存和空閒內存交錯在一塊兒,這時候須要一張列表來記錄哪些內存可以使用,在須要爲新生對象分配內存的時候,在這個列表中尋找一塊大小合適的內存分配給它便可。而java堆是否規整和垃圾收集器是否帶有壓縮整理功能有關。
在爲新生對象分配內存的時候,同時還須要考慮線程安全問題。由於在併發的狀況下內存分配並非線程安全的。有兩種方案解決這個線程安全問題,1、爲分配內存空間的動做進行同步處理;2、爲每一個線程預先分配一小塊內存,稱爲本地線程分配緩存(ThreadLocal Allocation Buffer, TLAB),哪一個線程須要分配內存,就在哪一個線程的TLAB上分配。
內存分配後,虛擬機須要將每一個對象分配到的內存初始化爲0值(不包括對象頭),這也就是爲何實例字段能夠不用初始化,直接爲0的緣由。
接來下,虛擬機對對象進行必要的設置,例如這個對象屬於哪一個類的實例,如何找到類的元數據信息。對象的哈希嗎、對象的GC年代等信息,這些信息都存放在對象頭之中。
執行完上面工做以後,全部的字段都爲0,接着執行<init>指令,把對象按照程序員的指令進行初始化,這樣一個對象就完整的建立出來。
即便在可達性分析算法中不可達的對象,也並不是「非死不可」的,這時候它們處於「緩刑」階段。一個對象的真正死亡,至少要經歷兩個標記過程:若是對象在進行可達性分析後發現沒有與GC Roots相鏈接的引用鏈,那它將會被第一次標記而且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,虛擬機將這兩種狀況視爲「沒有必要執行」。
若是這個對象被斷定爲有必要執行finalize()方法,那麼這個對象會被防止在一個叫作F-Queue的隊列中,並在稍後由一個虛擬機自動創建的、低優先級的Finalizer線程去執行它,但並不承諾會等待它運行結束,這樣作的緣由是,若是一個對象在finalize方法中執行的很慢,或者產生了死循環,將極可能到值F-Queue隊列中的其餘對象永久處於等待,甚至致使真個內存回收系統崩潰。
Finalize()方法是對象逃脫死亡的最後一次機會,稍後GC將對F-Queue中的對象進行第二次小規模標記,若是對象要在finalize()中成功拯救本身-----只須要從新與因用力按上的任何一個對象創建關聯即,好比把本身賦值給某個類變量或者對象的成員變量,那在第二次標記時它將被移出「即將回收」的集合;若是對象這時候尚未逃脫那基本上就真的被回收了。
各主要JVM啓動參數的做用以下:
-Xms:(堆內存初始值)Java Heap初始值,Server端JVM最好將-Xms和-Xmx設爲相同值,開發測試機JVM能夠保留默認值;
-Xmx:(堆內存最大值)Java Heap最大值,默認值爲物理內存的1/4,最佳設值應該視物理內存大小及計算機內其餘內存開銷而定;
-Xmn:(堆年輕代大小)Java HeapYoung區大小(統一最大最小都是它),不熟悉最好保留默認值;
-Xss:設置每一個線程的堆棧大小(也就是說,在相同物理內存下,減少這個值能生成更多的線程)
-XX:NewRatio:設置年輕代與老年代之比,如-XX:NewRatio=4就表示年輕代與老年代之比爲1:4
-XX:SurvivorRatio=8:設置新域中Eden區與Survivor區的比值。則比例爲1:8,總共1:1:8
十5、jvm調優工具與基本思路
工具備:jdk的bin目錄下命令行工具JStack,Jmap等,可視化工具JConsole、visualVM
查看堆空間大小分配(年輕代、年老代、持久代分配)
垃圾回收監控(長時間監控回收狀況)
線程信息監控:系統線程數量
線程狀態監控:各個線程都處在什麼樣的狀態下
線程詳細信息:查看線程內部運行狀況,死鎖檢查
CPU熱點:檢查系統哪些方法佔用了大量CPU時間
內存熱點:檢查哪些對象在系統中數量最大
大體能夠分爲3個過程:
1.解析與填充符號表過程。
--詞法、語法分析
--填充符號表
2.插入式註解處理器的註解處理過程。
3.語義分析與字節碼生成過程。
根據GC日誌狀況,新生代、老年代使用狀況而定….