首先我簡單來畫一張 JVM的結構原理圖,以下。java
咱們重點關注 JVM在運行時的數據區,你能夠看到在程序運行時,大體有5個部分。c++
1.方法區程序員
不止是存「方法」,而是存儲整個 class文件的信息,JVM運行時,類加載器子系統將會提取 class文件裏面的類信息,並將其存放在方法區中。例如類的名稱、類的類型(枚舉、類、接口)、字段、方法等等。objective-c
2.堆( Heap)算法
熟習 c/c++編程的同窗們應該至關熟習 Heap了,而對於Java而言,每一個應用都惟一對應一個JVM實例,而每個JVM實例惟一對應一個堆。堆主要包括關鍵字 new的對象實例、 this指針,或者者數組都放在堆中,並由應用全部的線程共享。堆由JVM的自動內存管理機制所管理,名爲垃圾回收—— GC(garbage collection)。編程
3.棧( Stack)後端
操做系統內核爲某個進程或者者線程創建的存儲區域,它保存着一個線程中的方法的調用狀態,它具有先進後出的特性。在棧中的數據大小與生命週期嚴格來講都是肯定的,例如在一個函數中公告的int變量即是存儲在 stack中,它的大小是固定的,在函數退出後它的生命週期也今後結束。在棧中,每個方法對應一個棧幀,JVM會對Java棧執行兩種操做:壓棧和出棧。這兩種操做在執行時都是以棧幀爲單位的。還有少許即時編譯器編譯後的代碼等數據。數組
4.PC寄存器bash
pc寄存器用於存放一條指令的地址,每個線程都有一個PC寄存器。服務器
5.本地方法棧
用來調用其他語言的本地方法,例如 C/C++寫的本地代碼, 這些方法在本地方法棧中執行,而不會在Java棧中執行。
自動垃圾回收機制,簡單來講就是尋覓 Java堆中的無用對象。打個好比:你的房間是JVM的內存,你在房間裏生活會製造垃圾和髒亂,而你媽就是 GC(聽起來有點像罵人)。你媽每時每刻都以爲你房間很髒亂,不時要把你趕出門打掃房間,假如你媽一直在房間打掃,那麼這個過程你沒法繼續在房間打遊戲吃泡麪。但假如你一直在房間,你的房間遲早要變成一個沒法居住的豬窩。
那麼,怎樣樣回收垃圾比較好呢?咱們大體能夠想出下面的思路。
Marking
首先,全部堆中的對象都會被掃描一遍:咱們總得知道哪些是垃圾,哪些是有用的物品吧。因爲垃圾實在太多了,因此,你媽會把全部的要扔掉的東西都找出來並打上一個標籤,到了時機成熟時回頭來一塊兒解決,這樣她就能解決你不須要的廢物、舊傢俱,而不是把你喜歡的衣服或者者身份證之類的東西扔掉。
Normal Deletion
垃圾收集器將清理掉標記的對象:你媽已經整理了一部分雜物(或者者已一概整理完),然後會將他們直接拎出去倒掉。你很開心房間又能夠繼續接受蹂躪了。
Deletion with Compacting
壓縮清理的方法:咱們知道,內存有空閒,並不表明着咱們就能使用它,例如咱們要分配數組這種一段連續空間,若是內存中碎片較多,必定是行不通的。正如房間可能須要再放一個新的牀,可是扔掉舊衣櫃後,原來的位置並不能放得下新牀,因此須要進行空間壓縮,把剩下的傢俱和物檔次置併到一塊兒,這樣就能騰出更多的空間啦。
有趣的是,JVM並非使用類似於 objective-c的 ARC(AutomaticReferenceCounting)的方式來引用計數對象,而是使用了叫根搜索算法( GC Root)的方法,基本思想就是選定少許對象做爲 GC Roots,並組成根對象集合,然後從這些做爲 GC Roots的對象做爲起始點,搜索所走過的引用鏈( ReferenceChain)。假如目標對象到 GC Roots是鏈接着的,咱們則稱該目標對象是可達的,假如目標對象不可達,則說明目標對象是能夠被回收的對象。
GC Root使用的算法是至關複雜的,你沒必要記住裏面的全部細節。可是你要知道的一點就是,能夠做爲 GC Root的對象能夠主要分爲四種。
JVM棧中引用的對象;
方法區中,靜態屬性引用的對象;
方法區中,常量引用的對象;
本地方法棧中,JNI(即Native方法)引用的對象;
在 JDK1.2以後,Java將引用分爲強引用、軟引用、弱引用、虛引用4種,這4種引用強度依次減弱。
嗯,聽起來這樣便可以了?可是實際狀況下,很不幸,在JVM中絕大部分對象都是英年早逝的,在編碼時大部分堆中的內存都是短暫臨時分配的,因此不管是效率仍是開銷方面,按上面那樣進行 GC每每是沒法知足咱們需求的。並且,實際上隨着分配的對象增多, GC的時間與開銷將會放大。因此,JVM的內存被分爲了三個主要部分:新生代,老年代和永久代。
新生代
全部新產生的對象一概都在新生代中, Eden區保存最新的對象,有兩個 SurvivorSpace—— S1和 S0,三個區域的比例大體爲 8:1:1。當新生代的 Eden區滿了,將觸發一次 GC,咱們把新生代中的 GC稱爲 minor garbage collections。minor garbage collections是一種 Stopthe world事件,比方你媽在打掃時,會把你趕出去,而不是你一邊扔垃圾她一邊打掃。
咱們來看下對象在堆中的分配過程,首先有新的對象進入時,默認放入新生代的 Eden區, S區都是默認爲空的。下面對象的數字表明經歷了多少次 GC,也就是對象的年齡。
當 eden區滿了,觸發 minor garbage collections,這時還有被引用的對象,就會被分配到 S0區域,剩下沒有被引用的對象就都會被清理。
再一次 GC時, S0區的部分對象極可能會出現沒有引用的,被引用的對象以及 S0中的存活對象,會被一塊兒移動到 S1中。eden和 S0中的未引用對象會被一概清理。
接下來就是無限循環上面的步驟了,當新生代中存活的對象超過了確定的【年齡】,會被分配至老年代的 Tenured區中。這個年齡能夠經過參數 MaxTenuringThreshold設定,默認值爲 15,圖中的例子爲 8次。
新生代管理內存採用的算法爲 GC複製算法( CopyingGC),也叫標記-複製法,原理是把內存分爲兩個空間:一個 From空間,一個 To空間,對象一開始只在 From空間分配, To空間是空閒的。GC時把存活的對象從 From空間複製粘貼到 To空間,以後把 To空間變成新的 From空間,原來的 From空間變成 To空間。
首先標記不可達對象。
然後移動存活的對象到 to區,並保證他們在內存中連續。
清掃垃圾。
能夠看到上圖操做後內存幾乎都是連續的,因此它的效率是很是高的,可是相對的吞吐量會較大。而且,把內存一分爲二,佔用了將近一半的可用內存。用一段僞代碼來實現大體爲下。
<code>
void copying(){
$free = $to_start
// $free表示To區佔用偏移量,每複製成功一個對象obj,
// $free向前移動size(obj)
for(r : $roots)
*r = copy(*r) // 複製成功後返回新的引用
swap($from_start, $to_start) // GC完成後交互From區與To區的指針
}
</code>複製代碼
老年代
老年代用來存儲活時間較長的對象,老年代區域的 GC是 major garbage collection,老年代中的內存不夠時,就會觸發一次。這也是一個 Stopthe world事件,可是看名字就知道,這個回收過程會至關慢,因爲這包括了對新生代和老年代全部對象的回收,也叫 FullGC。
老年代管理內存最先採用的算法爲標記-清除算法,這個算法很好了解,結合 GC Root的定義,咱們會把全部不可達的對象一概標記進行清理。
在清理前,黃色的爲不可達對象。
在清理後,一概都變成可達對象。
那麼,這個算法的劣勢很好了解:對,會在標記清理的過程當中產生大量的內存碎片,Java在分配內存時一般是按連續內存分配,這樣咱們會白費不少內存。因此,如今的 JVM GC在老年代都是使用標記-壓縮清理方法,將上圖在清理後的內存進行整理和壓縮,以保證內存連續,儘管這個算法的效率是三種算法裏最低的。
永久代
永久代位於方法區,主要存放元數據,例如 Class、 Method的元信息,與 GC要回收的對象其實關係並非很大,咱們能夠幾乎忽略其對 GC的影響。除了 JavaHotSpot這種較新的虛擬機技術,會回收無用的常量和的類,以避免大量運用反射這類頻繁本身設置 ClassLoader的操做時方法區溢出。
通常而言, GC不該該成爲影響系統性能的瓶頸,咱們在評估 GC收集器的優劣時通常考慮如下幾點:
吞吐量
GC開銷
暫停時間
GC頻率
堆空間
對象生命週期
因此針對不一樣的 GC收集器,咱們要對應咱們的應用場景來進行選擇和調優,回顧 GC的歷史,主要有 4種 GC收集器: Serial、 Parallel、 CMS和 G1。
Serial
Serial收集器使用了標記-複製的算法,能夠用 -XX:+UseSerialGC使用單線程的串行收集器。可是在 GC進行時,程序會進入長時間的暫停時間,通常不太建議使用。
Parallel
-XX:+UseParallelGC-XX:+UseParallelOldGCParallel也使用了標記-複製的算法,可是咱們稱之爲吞吐量優先的收集器,因爲 Parallel最主要的優點在於並行使用多線程去完成垃圾清除工做,這樣能夠充分利用多核的特性,大幅下降 gc時間。當你的程序場景吞吐量較大,例如消息隊列這種應用,須要保證有效利用 CPU資源,能夠忍受確定的停頓時間,能夠優先考慮這種方式。
CMS ( ConcurrentMarkSweep)
-XX:+UseParNewGC-XX:+UseConcMarkSweepGCCMS使用了標記-清理的算法,當應用尤爲重視服務器的響應速度(比方 Apiserver),但願系統停頓時間最短,以給客戶帶來較好的體驗,那麼能夠選擇 CMS。CMS收集器在 MinorGC時會暫停全部的應用線程,並以多線程的方式進行垃圾回收。在 FullGC時不暫停應用線程,而是使用若干個後端線程按期的對老年代空間進行掃描,及時回收其中再也不使用的對象。
G1( GarbageFirst)
-XX:+UseG1GC 在堆比較大的時候,假如 full gc頻繁,會致使停頓,而且調用方阻塞、超時、甚至雪崩的狀況出現,因此下降 full gc的發生頻率和須要時間,很是有必要。G1的誕生正是爲了下降 FullGC的次數,而相較於 CMS, G1使用了標記-壓縮清理算法,這能夠大大下降較大內存( 4GB以上) GC時產生的內存碎片。
G1提供了兩種 GC模式, YoungGC和 MixedGC,兩種都是 StopTheWorld(STW)的。YoungGC主要是對 Eden區進行 GC, MixGC不只進行正常的新生代垃圾收集,同時也回收部分後端掃描線程標記的老年代分區。
另外有趣的一點, G1將新生代、老年代的物理空間劃分取消了,而是將堆劃分爲若干個區域( region),每一個大小都爲 2的倍數且大小一概一致,最多有 2000個。除此以外, G1專門劃分了一個 Humongous區,它用來專門存放超過一個 region 50%大小的巨型對象。在正常的解決過程當中,對象從一個區域複製到另一個區域,同時也完成了堆的壓縮。
經常使用參數
-XX:+UseSerialGC:在新生代和老年代使用串行收集器
-XX:+UseParNewGC:在新生代使用並行收集器
-XX:+UseParallelGC :新生代使用並行回收收集器,更加關注吞吐量
-XX:+UseParallelOldGC:老年代使用並行回收收集器
-XX:ParallelGCThreads:設置用於垃圾回收的線程數
-XX:+UseConcMarkSweepGC:新生代使用並行收集器,老年代使用CMS+串行收集器
-XX:ParallelCMSThreads:設定CMS的線程數量
-XX:+UseG1GC:啓用G1垃圾回收器
舉個實例Student stu=new Student();
這份代碼中Student stu是一個引用變量因此存放在java虛擬機棧上,new Student()是一個實例對象存放在java堆上。另外,在Java 堆中還必須包含能查找到此對象類型數據(如對象類型、父類、實現的接口、方法等)的地址信息,這些類型數據則存儲在方法區中。
因爲reference 類型在Java 虛擬機規範裏面只規定了一個指向對象的引用,並無定義這個引用應該經過哪一種方式去定位,以及訪問到Java 堆中的對象的具體位置,所以不一樣虛擬機實現的對象訪問方式會有所不一樣,主流的訪問方式有兩種:使用句柄和直接指針。若是使用句柄訪問方式Java 堆中將會劃分出一塊內存來做爲句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據和類型數據各自的具體地址信息,以下圖所示。
指針方式
Java 堆對象的佈局中就必須考慮如何放置訪問類型
這兩種對象的訪問方式各有優點,使用句柄訪問方式的最大好處就是reference 中存儲的是穩定的句柄地址,在對象被移動(垃圾收集時移動對象是很是廣泛的行爲)時只會改變句柄中的實例數據指針,而引用對象自己不須要被修改。使用直接指針訪問方式的最大好處就是速度更快,它節省了一次指針定位的時間開銷,因爲對象的訪問在Java 中很是頻繁,所以這類開銷聚沙成塔後也是一項很是可觀的執行成本。
若是您感受文章對您有所幫助,請讓更多人看到!
1.點贊此篇文章,並評論一句!
2.轉發此篇文章 給予做者支持!
3.微信搜索 ~ 關注微信公衆號:程序員知識碼頭 獲取全套學習資料一份!
微信掃碼關注:天天準時發技術文章!還能加入專屬的學習交流社羣!
點贊!好文要頂哦~